Skip to content

Commit a239706

Browse files
committed
Implemented caching for cohort def list, concept set list, permissions.
Implementation uses ehCache 3.9. Added test for CohortDefinitionService caching.
1 parent 21f2f4b commit a239706

18 files changed

+520
-80
lines changed

pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@
236236
<logging.level.org.hibernate>info</logging.level.org.hibernate>
237237
<logging.level.org.apache.shiro>warn</logging.level.org.apache.shiro>
238238

239+
<!-- Spring Cache properties -->
240+
<spring.cache.type>jcache</spring.cache.type>
241+
242+
<!-- Spring Batch -->
239243
<spring.batch.taskExecutor.corePoolSize>10</spring.batch.taskExecutor.corePoolSize>
240244
<spring.batch.taskExecutor.maxPoolSize>20</spring.batch.taskExecutor.maxPoolSize>
241245
<spring.batch.taskExecutor.queueCapacity>2147483647</spring.batch.taskExecutor.queueCapacity>
@@ -272,6 +276,7 @@
272276
<cache.jobs.count>3</cache.jobs.count>
273277
<!-- Achilles cache -->
274278
<cache.achilles.usePersonCount>true</cache.achilles.usePersonCount>
279+
<cache.webapi.enabled>true</cache.webapi.enabled>
275280

276281
<!-- Build info -->
277282
<buildinfo.atlas.milestone.id>47</buildinfo.atlas.milestone.id>
@@ -742,6 +747,10 @@
742747
<scope>provided</scope>
743748
</dependency>
744749
<dependency>
750+
<groupId>javax.cache</groupId>
751+
<artifactId>cache-api</artifactId>
752+
<version>1.1.1</version>
753+
</dependency> <dependency>
745754
<groupId>org.ohdsi.sql</groupId>
746755
<artifactId>SqlRender</artifactId>
747756
<version>${SqlRender.version}</version>
@@ -1189,6 +1198,10 @@
11891198
<version>1.1.7</version>
11901199
</dependency>
11911200
<dependency>
1201+
<groupId>org.ehcache</groupId>
1202+
<artifactId>ehcache</artifactId>
1203+
<version>3.9.11</version>
1204+
</dependency> <dependency>
11921205
<groupId>com.opentable.components</groupId>
11931206
<artifactId>otj-pg-embedded</artifactId>
11941207
<version>0.13.1</version>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.ohdsi.webapi;
2+
3+
import org.springframework.cache.annotation.EnableCaching;
4+
import org.springframework.context.annotation.Configuration;
5+
6+
@Configuration
7+
@EnableCaching
8+
public class CacheConfig {
9+
10+
}

src/main/java/org/ohdsi/webapi/JerseyConfig.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
import javax.inject.Singleton;
3939
import javax.ws.rs.ext.RuntimeDelegate;
40+
import org.ohdsi.webapi.cache.CacheService;
4041

4142
/**
4243
*
@@ -59,31 +60,32 @@ public JerseyConfig() {
5960
public void afterPropertiesSet() throws Exception {
6061
packages(this.rootPackage);
6162
register(ActivityService.class);
63+
register(CacheService.class);
64+
register(CcController.class);
6265
register(CDMResultsService.class);
6366
register(CohortAnalysisService.class);
6467
register(CohortDefinitionService.class);
6568
register(CohortResultsService.class);
6669
register(CohortService.class);
6770
register(ConceptSetService.class);
71+
register(DDLService.class);
6872
register(EvidenceService.class);
6973
register(FeasibilityService.class);
74+
register(FeatureExtractionService.class);
7075
register(InfoService.class);
7176
register(IRAnalysisResource.class);
7277
register(JobService.class);
78+
register(MultiPartFeature.class);
79+
register(PermissionController.class);
7380
register(PersonService.class);
81+
register(ScriptExecutionController.class);
82+
register(ScriptExecutionCallbackController.class);
7483
register(SourceController.class);
7584
register(SqlRenderService.class);
76-
register(DDLService.class);
85+
register(SSOController.class);
7786
register(TherapyPathResultsService.class);
7887
register(UserService.class);
7988
register(VocabularyService.class);
80-
register(ScriptExecutionController.class);
81-
register(ScriptExecutionCallbackController.class);
82-
register(MultiPartFeature.class);
83-
register(FeatureExtractionService.class);
84-
register(CcController.class);
85-
register(SSOController.class);
86-
register(PermissionController.class);
8789
register(new AbstractBinder() {
8890
@Override
8991
protected void configure() {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2019 cknoll1.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.ohdsi.webapi.cache;
17+
18+
import java.io.Serializable;
19+
import java.util.Optional;
20+
import javax.cache.management.CacheStatisticsMXBean;
21+
22+
/**
23+
*
24+
* @author cknoll1
25+
*/
26+
public class CacheInfo implements Serializable{
27+
public String cacheName;
28+
public Long entries;
29+
public CacheStatisticsMXBean cacheStatistics;
30+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2019 cknoll1.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.ohdsi.webapi.cache;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Optional;
21+
import java.util.stream.StreamSupport;
22+
import javax.cache.Cache;
23+
import javax.cache.CacheManager;
24+
import javax.ws.rs.GET;
25+
import javax.ws.rs.Path;
26+
import javax.ws.rs.Produces;
27+
import javax.ws.rs.core.MediaType;
28+
import org.ohdsi.webapi.util.CacheHelper;
29+
import org.springframework.beans.factory.annotation.Autowired;
30+
import org.springframework.stereotype.Component;
31+
32+
/**
33+
*
34+
* @author cknoll1
35+
*/
36+
@Path("/cache")
37+
@Component
38+
public class CacheService {
39+
40+
public static class ClearCacheResult {
41+
42+
public List<CacheInfo> clearedCaches;
43+
44+
private ClearCacheResult() {
45+
this.clearedCaches = new ArrayList<>();
46+
}
47+
}
48+
49+
private CacheManager cacheManager;
50+
51+
@Autowired(required = false)
52+
public CacheService(CacheManager cacheManager) {
53+
54+
this.cacheManager = cacheManager;
55+
}
56+
57+
@GET
58+
@Path("/")
59+
@Produces(MediaType.APPLICATION_JSON)
60+
public List<CacheInfo> getCacheInfoList() {
61+
List<CacheInfo> caches = new ArrayList<>();
62+
63+
if (cacheManager == null) return caches; //caching is disabled
64+
65+
for (String cacheName : cacheManager.getCacheNames()) {
66+
Cache cache = cacheManager.getCache(cacheName);
67+
CacheInfo info = new CacheInfo();
68+
info.cacheName = cacheName;
69+
info.entries = StreamSupport.stream(cache.spliterator(), false).count();
70+
info.cacheStatistics = CacheHelper.getCacheStats(cacheManager , cacheName);
71+
caches.add(info);
72+
}
73+
return caches;
74+
}
75+
@GET
76+
@Path("/clear")
77+
@Produces(MediaType.APPLICATION_JSON)
78+
public ClearCacheResult clearAll() {
79+
ClearCacheResult result = new ClearCacheResult();
80+
81+
for (String cacheName : cacheManager.getCacheNames()) {
82+
Cache cache = cacheManager.getCache(cacheName);
83+
CacheInfo info = new CacheInfo();
84+
info.cacheName = cacheName;
85+
info.entries = StreamSupport.stream(cache.spliterator(), false).count();
86+
result.clearedCaches.add(info);
87+
cache.clear();
88+
}
89+
return result;
90+
}
91+
92+
}

src/main/java/org/ohdsi/webapi/security/PermissionService.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ public class PermissionService {
6262
@Value("#{!'${security.provider}'.equals('DisabledSecurity')}")
6363
private boolean securityEnabled;
6464

65+
@Value("${security.defaultGlobalReadPermissions}")
66+
private boolean defaultGlobalReadPermissions;
67+
6568
private final EntityGraph PERMISSION_ENTITY_GRAPH = EntityGraphUtils.fromAttributePaths("rolePermissions", "rolePermissions.role");
6669

6770
public PermissionService(
@@ -227,4 +230,17 @@ public void fillReadAccess(CommonEntity entity, CommonEntityDTO entityDTO) {
227230
public boolean isSecurityEnabled() {
228231
return this.securityEnabled;
229232
}
233+
234+
// Use this key for cache (asset lists) that may be associated to a user or shared across users.
235+
public String getAssetListCacheKey() {
236+
if (this.isSecurityEnabled() && !defaultGlobalReadPermissions)
237+
return permissionManager.getSubjectName();
238+
else
239+
return "ALL_USERS";
240+
}
241+
242+
// use this cache key when the cache is associated to a user
243+
public String getSubjectCacheKey() {
244+
return this.isSecurityEnabled() ? permissionManager.getSubjectName() : "ALL_USERS";
245+
}
230246
}

src/main/java/org/ohdsi/webapi/service/CohortDefinitionService.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,18 @@
131131
import java.util.Optional;
132132
import java.util.Set;
133133
import java.util.stream.Collectors;
134+
import javax.cache.CacheManager;
135+
import javax.cache.configuration.MutableConfiguration;
134136
import javax.ws.rs.core.Response.ResponseBuilder;
135137

136138
import static org.ohdsi.webapi.Constants.Params.COHORT_DEFINITION_ID;
137139
import static org.ohdsi.webapi.Constants.Params.JOB_NAME;
138140
import static org.ohdsi.webapi.Constants.Params.SOURCE_ID;
139141
import org.ohdsi.webapi.source.SourceService;
140142
import static org.ohdsi.webapi.util.SecurityUtils.whitelist;
143+
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
144+
import org.springframework.cache.annotation.CacheEvict;
145+
import org.springframework.cache.annotation.Cacheable;
141146

142147
/**
143148
* Provides REST services for working with cohort definitions.
@@ -149,6 +154,22 @@
149154
@Component
150155
public class CohortDefinitionService extends AbstractDaoService implements HasTags<Integer> {
151156

157+
//create cache
158+
@Component
159+
public static class CachingSetup implements JCacheManagerCustomizer {
160+
161+
public static final String COHORT_DEFINITION_LIST_CACHE = "cohortDefinitionList";
162+
163+
@Override
164+
public void customize(CacheManager cacheManager) {
165+
// Evict when a cohort definition is created or updated, or permissions, or tags
166+
cacheManager.createCache(COHORT_DEFINITION_LIST_CACHE, new MutableConfiguration<String, List<CohortMetadataDTO>>()
167+
.setTypes(String.class, (Class<List<CohortMetadataDTO>>) (Class<?>) List.class)
168+
.setStoreByValue(false)
169+
.setStatisticsEnabled(true));
170+
}
171+
}
172+
152173
private static final CohortExpressionQueryBuilder queryBuilder = new CohortExpressionQueryBuilder();
153174

154175
@Autowired
@@ -205,7 +226,7 @@ public class CohortDefinitionService extends AbstractDaoService implements HasTa
205226
@Autowired
206227
private VersionService<CohortVersion> versionService;
207228

208-
@Value("${security.defaultGlobalReadPermissions}")
229+
@Value("${security.defaultGlobalReadPermissions}")
209230
private boolean defaultGlobalReadPermissions;
210231

211232
private final MarkdownRender markdownPF = new MarkdownRender();
@@ -408,6 +429,7 @@ public GenerateSqlResult generateSql(GenerateSqlRequest request) {
408429
@Path("/")
409430
@Produces(MediaType.APPLICATION_JSON)
410431
@Transactional
432+
@Cacheable(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, key = "@permissionService.getAssetListCacheKey()")
411433
public List<CohortMetadataDTO> getCohortDefinitionList() {
412434
List<CohortDefinition> definitions = cohortDefinitionRepository.list();
413435
return definitions.stream()
@@ -436,6 +458,7 @@ public List<CohortMetadataDTO> getCohortDefinitionList() {
436458
@Transactional
437459
@Produces(MediaType.APPLICATION_JSON)
438460
@Consumes(MediaType.APPLICATION_JSON)
461+
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
439462
public CohortDTO createCohortDefinition(CohortDTO dto) {
440463

441464
Date currentTime = Calendar.getInstance().getTime();
@@ -538,6 +561,7 @@ public int getCountCDefWithSameName(@PathParam("id") @DefaultValue("0") final in
538561
@Produces(MediaType.APPLICATION_JSON)
539562
@Consumes(MediaType.APPLICATION_JSON)
540563
@Transactional
564+
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
541565
public CohortDTO saveCohortDefinition(@PathParam("id") final int id, CohortDTO def) {
542566
Date currentTime = Calendar.getInstance().getTime();
543567

@@ -670,6 +694,7 @@ public List<CohortGenerationInfoDTO> getInfo(@PathParam("id") final int id) {
670694
@Produces(MediaType.APPLICATION_JSON)
671695
@Path("/{id}/copy")
672696
@Transactional
697+
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
673698
public CohortDTO copy(@PathParam("id") final int id) {
674699
CohortDTO sourceDef = getCohortDefinition(id);
675700
sourceDef.setId(null); // clear the ID
@@ -954,6 +979,7 @@ private Response printFrindly(String markdown, String format) {
954979
@POST
955980
@Produces(MediaType.APPLICATION_JSON)
956981
@Path("/{id}/tag/")
982+
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
957983
@Transactional
958984
public void assignTag(@PathParam("id") final Integer id, final int tagId) {
959985
CohortDefinition entity = cohortDefinitionRepository.findOne(id);
@@ -971,6 +997,7 @@ public void assignTag(@PathParam("id") final Integer id, final int tagId) {
971997
@DELETE
972998
@Produces(MediaType.APPLICATION_JSON)
973999
@Path("/{id}/tag/{tagId}")
1000+
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
9741001
@Transactional
9751002
public void unassignTag(@PathParam("id") final Integer id, @PathParam("tagId") final int tagId) {
9761003
CohortDefinition entity = cohortDefinitionRepository.findOne(id);
@@ -1106,6 +1133,7 @@ public void deleteVersion(@PathParam("id") final int id, @PathParam("version") f
11061133
@Produces(MediaType.APPLICATION_JSON)
11071134
@Path("/{id}/version/{version}/createAsset")
11081135
@Transactional
1136+
@CacheEvict(cacheNames = CachingSetup.COHORT_DEFINITION_LIST_CACHE, allEntries = true)
11091137
public CohortDTO copyAssetFromVersion(@PathParam("id") final int id, @PathParam("version") final int version) {
11101138
checkVersion(id, version, false);
11111139
CohortVersion cohortVersion = versionService.getById(VersionType.COHORT, id, version);

0 commit comments

Comments
 (0)