diff --git a/src/main/java/org/cbioportal/service/impl/vs/VSAwareServicesConfiguration.java b/src/main/java/org/cbioportal/service/impl/vs/VSAwareServicesConfiguration.java index 8307c5e3a58..3225756ba08 100644 --- a/src/main/java/org/cbioportal/service/impl/vs/VSAwareServicesConfiguration.java +++ b/src/main/java/org/cbioportal/service/impl/vs/VSAwareServicesConfiguration.java @@ -8,6 +8,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; + +import java.util.concurrent.Executor; @Configuration @ConditionalOnProperty(name = "vs_mode", havingValue = "true") @@ -19,9 +23,20 @@ public class VSAwareServicesConfiguration { @Autowired private ReadPermissionService readPermissionService; + @Bean + public Executor asyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(20); + executor.setQueueCapacity(50); + executor.setThreadNamePrefix("AsyncThread-"); + executor.initialize(); + return new DelegatingSecurityContextExecutor(executor); + } + @Primary @Bean - public StudyService studyService(StudyService studyService) { - return new VSAwareStudyServiceImpl(studyService, sessionServiceRequestHandler, readPermissionService); + public StudyService studyService(StudyService studyService, Executor asyncExecutor) { + return new VSAwareStudyServiceImpl(studyService, sessionServiceRequestHandler, readPermissionService, asyncExecutor); } } diff --git a/src/main/java/org/cbioportal/service/impl/vs/VSAwareStudyServiceImpl.java b/src/main/java/org/cbioportal/service/impl/vs/VSAwareStudyServiceImpl.java index 111fd59da7b..507c2fcc6b3 100644 --- a/src/main/java/org/cbioportal/service/impl/vs/VSAwareStudyServiceImpl.java +++ b/src/main/java/org/cbioportal/service/impl/vs/VSAwareStudyServiceImpl.java @@ -10,12 +10,14 @@ import org.cbioportal.utils.security.AccessLevel; import org.cbioportal.web.parameter.VirtualStudy; import org.cbioportal.web.parameter.VirtualStudyData; +import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.Authentication; import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Stream; @@ -26,18 +28,19 @@ public class VSAwareStudyServiceImpl implements StudyService { private final SessionServiceRequestHandler sessionServiceRequestHandler; private final ReadPermissionService readPermissionService; + private final Executor asyncExecutor; - public VSAwareStudyServiceImpl(StudyService studyService, SessionServiceRequestHandler sessionServiceRequestHandler, ReadPermissionService readPermissionService) { + public VSAwareStudyServiceImpl(StudyService studyService, SessionServiceRequestHandler sessionServiceRequestHandler, ReadPermissionService readPermissionService, Executor asyncExecutor) { this.studyService = studyService; this.sessionServiceRequestHandler = sessionServiceRequestHandler; this.readPermissionService = readPermissionService; + this.asyncExecutor = asyncExecutor; } @Override public List getAllStudies(String keyword, String projection, Integer pageSize, Integer pageNumber, String sortBy, String direction, Authentication authentication, AccessLevel accessLevel) { - CompletableFuture> materialisedStudies = CompletableFuture.supplyAsync(() -> studyService.getAllStudies(keyword, projection, null, null, null, null, authentication, accessLevel)); - CompletableFuture> virtualStudies = CompletableFuture.supplyAsync(() -> sessionServiceRequestHandler.getVirtualStudiesAccessibleToUser("*").stream() - .map(VSAwareStudyServiceImpl::toCancerStudy).filter(cs -> shouldSelect(cs, keyword)).toList()); + CompletableFuture> materialisedStudies = getMaterialisedStudiesAsync(keyword, projection, authentication, accessLevel); + CompletableFuture> virtualStudies = getVirtualStudiesAsync(keyword); Stream resultStream = Stream.concat( materialisedStudies.join().stream(), @@ -49,7 +52,7 @@ public List getAllStudies(String keyword, String projection, Intege } if (pageSize != null && pageNumber != null) { - resultStream = resultStream.skip((long) pageSize * (pageNumber - 1)).limit(pageSize); + resultStream = resultStream.skip((long) pageSize * pageNumber).limit(pageSize); } List result = resultStream.toList(); @@ -57,6 +60,17 @@ public List getAllStudies(String keyword, String projection, Intege return result; } + @Async + private CompletableFuture> getVirtualStudiesAsync(String keyword) { + return CompletableFuture.supplyAsync(() -> sessionServiceRequestHandler.getVirtualStudiesAccessibleToUser("*").stream() + .map(VSAwareStudyServiceImpl::toCancerStudy).filter(cs -> shouldSelect(cs, keyword)).toList(), asyncExecutor); + } + + @Async + private CompletableFuture> getMaterialisedStudiesAsync(String keyword, String projection, Authentication authentication, AccessLevel accessLevel) { + return CompletableFuture.supplyAsync(() -> studyService.getAllStudies(keyword, projection, null, null, null, null, authentication, accessLevel), asyncExecutor); + } + private static CancerStudy toCancerStudy(VirtualStudy vs) { VirtualStudyData vsd = vs.getData(); CancerStudy cs = new CancerStudy(); @@ -98,15 +112,25 @@ public BaseMeta getMetaStudies(String keyword) { @Override public CancerStudy getStudy(String studyId) throws StudyNotFoundException { - CompletableFuture> materialisedStudy = CompletableFuture.supplyAsync(() -> { + CompletableFuture> materialisedStudy = getMaterialisedStudyAsync(studyId); + CompletableFuture> virtualStudy = getVirtualStudyAsync(studyId); + return firstPresent(materialisedStudy, virtualStudy).join().orElseThrow(() -> new StudyNotFoundException(studyId)); + } + + @Async + private CompletableFuture> getVirtualStudyAsync(String studyId) { + return CompletableFuture.supplyAsync(() -> Optional.ofNullable(sessionServiceRequestHandler.getVirtualStudyById(studyId)).map(VSAwareStudyServiceImpl::toCancerStudy), asyncExecutor); + } + + @Async + private CompletableFuture> getMaterialisedStudyAsync(String studyId) { + return CompletableFuture.supplyAsync(() -> { try { return Optional.of(studyService.getStudy(studyId)); } catch (StudyNotFoundException e) { return Optional.empty(); } - }); - CompletableFuture> virtualStudy = CompletableFuture.supplyAsync(() -> Optional.ofNullable(sessionServiceRequestHandler.getVirtualStudyById(studyId)).map(VSAwareStudyServiceImpl::toCancerStudy)); - return firstPresent(materialisedStudy, virtualStudy).join().orElseThrow(() -> new StudyNotFoundException(studyId)); + }, asyncExecutor); } private static CompletableFuture> firstPresent( diff --git a/src/main/java/org/cbioportal/web/PublicVirtualStudiesController.java b/src/main/java/org/cbioportal/web/PublicVirtualStudiesController.java index e4f8ce647e1..8cbb59d9e10 100644 --- a/src/main/java/org/cbioportal/web/PublicVirtualStudiesController.java +++ b/src/main/java/org/cbioportal/web/PublicVirtualStudiesController.java @@ -47,7 +47,7 @@ public PublicVirtualStudiesController( @Value("${session.endpoint.publisher-api-key:}") String requiredPublisherApiKey, SessionServiceRequestHandler sessionServiceRequestHandler, CancerTypeService cancerTypeService, - @Value("vs_mode") Boolean vsMode + @Value("${vs_mode:false}") Boolean vsMode ) { this.requiredPublisherApiKey = requiredPublisherApiKey; this.sessionServiceRequestHandler = sessionServiceRequestHandler;