Skip to content

Commit

Permalink
Add Columnar Study View Controller /api/column-store
Browse files Browse the repository at this point in the history
  • Loading branch information
haynescd committed Apr 8, 2024
1 parent 7d8b9f7 commit 8b07d3f
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -15,7 +16,7 @@


@Configuration
@MapperScan("org.cbioportal.persistence.mybatis")
@MapperScan(value="org.cbioportal.persistence.mybatis", sqlSessionFactoryRef="sqlSessionFactory")
public class PersistenceConfig {

// This is the only way I was able to register the SampleType TypeHandler to MyBatis.
Expand All @@ -31,7 +32,7 @@ public void customize(org.apache.ibatis.session.Configuration configuration) {
};
}

@Bean
@Bean("sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource, ApplicationContext applicationContext) throws IOException {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@


@Configuration
@MapperScan("org.cbioportal.persistence.mybatiscolumnar")
public class PersistenceConfig {
@MapperScan(value="org.cbioportal.persistence.mybatiscolumnar", sqlSessionFactoryRef ="sqlColumnarSessionFactory")
public class PersistenceColumnarConfig {

@Bean
public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("columnarDataSource") DataSource dataSource, ApplicationContext applicationContext) throws IOException {
@Bean("sqlColumnarSessionFactory")
public SqlSessionFactoryBean sqlColumnarSessionFactory(@Qualifier("columnarDataSource") DataSource dataSource, ApplicationContext applicationContext) throws IOException {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setMapperLocations(
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/org/cbioportal/service/StudyViewColumnarService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.cbioportal.service;

import org.cbioportal.model.AlterationCountByGene;
import org.cbioportal.model.ClinicalData;
import org.cbioportal.model.ClinicalDataCountItem;
import org.cbioportal.model.Sample;
import org.cbioportal.web.parameter.StudyViewFilter;

import java.util.List;

public interface StudyViewColumnarService {

List<Sample> getFilteredSamples(StudyViewFilter studyViewFilter);

List<AlterationCountByGene> getMutatedGenes(StudyViewFilter interceptedStudyViewFilter);

List<ClinicalDataCountItem> getClinicalDataCounts(StudyViewFilter studyViewFilter, List<String> filteredAttributes);

List<ClinicalData> getPatientClinicalData(StudyViewFilter studyViewFilter, List<String> attributeIds);

List<ClinicalData> getSampleClinicalData(StudyViewFilter studyViewFilter, List<String> attributeIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.cbioportal.service.impl;

import org.cbioportal.model.AlterationCountByGene;
import org.cbioportal.model.ClinicalData;
import org.cbioportal.model.ClinicalDataCount;
import org.cbioportal.model.ClinicalDataCountItem;
import org.cbioportal.model.Sample;
import org.cbioportal.persistence.StudyViewRepository;
import org.cbioportal.persistence.enums.ClinicalAttributeDataSource;
import org.cbioportal.persistence.enums.ClinicalAttributeDataType;
import org.cbioportal.service.StudyViewColumnarService;
import org.cbioportal.web.parameter.CategorizedClinicalDataCountFilter;
import org.cbioportal.web.parameter.StudyViewFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class StudyViewColumnarServiceImpl implements StudyViewColumnarService {

private final Map<String, List<String>> clinicalAttributeNameMap = new HashMap<>();


private final StudyViewRepository studyViewRepository;

@Autowired
public StudyViewColumnarServiceImpl(StudyViewRepository studyViewRepository) {
this.studyViewRepository = studyViewRepository;
}

@Override
public List<Sample> getFilteredSamples(StudyViewFilter studyViewFilter) {
CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter = extractClinicalDataCountFilters(studyViewFilter);
return studyViewRepository.getFilteredSamples(studyViewFilter, categorizedClinicalDataCountFilter);
}

@Override
public List<AlterationCountByGene> getMutatedGenes(StudyViewFilter studyViewFilter) {
CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter = extractClinicalDataCountFilters(studyViewFilter);
return studyViewRepository.getMutatedGenes(studyViewFilter, categorizedClinicalDataCountFilter);
}

@Override
public List<ClinicalDataCountItem> getClinicalDataCounts(StudyViewFilter studyViewFilter, List<String> filteredAttributes) {
CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter = extractClinicalDataCountFilters(studyViewFilter);

return studyViewRepository.getClinicalDataCounts(studyViewFilter, categorizedClinicalDataCountFilter, filteredAttributes)
.stream().collect(Collectors.groupingBy(ClinicalDataCount::getAttributeId))
.entrySet().parallelStream().map(e -> {
ClinicalDataCountItem item = new ClinicalDataCountItem();
item.setAttributeId(e.getKey());
item.setCounts(e.getValue());
return item;
}).collect(Collectors.toList());
}

private CategorizedClinicalDataCountFilter extractClinicalDataCountFilters(final StudyViewFilter studyViewFilter) {
if(clinicalAttributeNameMap.isEmpty()) {
buildClinicalAttributeNameMap();
}

if(studyViewFilter.getClinicalDataFilters() == null) {
return CategorizedClinicalDataCountFilter.getBuilder().build();
}

final String patientCategoricalKey = ClinicalAttributeDataSource.PATIENT.getValue() + ClinicalAttributeDataType.CATEGORICAL.getValue();
final String patientNumericKey = ClinicalAttributeDataSource.PATIENT.getValue() + ClinicalAttributeDataType.NUMERIC.getValue();
final String sampleCategoricalKey = ClinicalAttributeDataSource.SAMPLE.getValue() + ClinicalAttributeDataType.CATEGORICAL.getValue();
final String sampleNumericKey = ClinicalAttributeDataSource.SAMPLE.getValue() + ClinicalAttributeDataType.NUMERIC.getValue();

return CategorizedClinicalDataCountFilter.getBuilder()
.setPatientCategoricalClinicalDataFilters(studyViewFilter.getClinicalDataFilters()
.stream().filter(clinicalDataFilter -> clinicalAttributeNameMap.get(patientCategoricalKey).contains(clinicalDataFilter.getAttributeId()))
.collect(Collectors.toList()))
.setPatientNumericalClinicalDataFilters(studyViewFilter.getClinicalDataFilters().stream()
.filter(clinicalDataFilter -> clinicalAttributeNameMap.get(patientNumericKey).contains(clinicalDataFilter.getAttributeId()))
.collect(Collectors.toList()))
.setSampleCategoricalClinicalDataFilters(studyViewFilter.getClinicalDataFilters().stream()
.filter(clinicalDataFilter -> clinicalAttributeNameMap.get(sampleCategoricalKey).contains(clinicalDataFilter.getAttributeId()))
.collect(Collectors.toList()))
.setSampleNumericalClinicalDataFilters(studyViewFilter.getClinicalDataFilters().stream()
.filter(clinicalDataFilter -> clinicalAttributeNameMap.get(sampleNumericKey).contains(clinicalDataFilter.getAttributeId()))
.collect(Collectors.toList()))
.build();
}

private void buildClinicalAttributeNameMap() {
List<ClinicalAttributeDataSource> clinicalAttributeDataSources = List.of(ClinicalAttributeDataSource.values());
for(ClinicalAttributeDataSource clinicalAttributeDataSource : clinicalAttributeDataSources) {
String categoricalKey = clinicalAttributeDataSource.getValue() + ClinicalAttributeDataType.CATEGORICAL;
String numericKey = clinicalAttributeDataSource.getValue() + ClinicalAttributeDataType.NUMERIC;
clinicalAttributeNameMap.put(categoricalKey, studyViewRepository.getClinicalDataAttributeNames(clinicalAttributeDataSource, ClinicalAttributeDataType.CATEGORICAL));
clinicalAttributeNameMap.put(numericKey, studyViewRepository.getClinicalDataAttributeNames(clinicalAttributeDataSource, ClinicalAttributeDataType.NUMERIC));
}
}

@Override
public List<ClinicalData> getPatientClinicalData(StudyViewFilter studyViewFilter, List<String> attributeIds) {
CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter = extractClinicalDataCountFilters(studyViewFilter);
return studyViewRepository.getPatientClinicalData(studyViewFilter, attributeIds, categorizedClinicalDataCountFilter);
}

@Override
public List<ClinicalData> getSampleClinicalData(StudyViewFilter studyViewFilter, List<String> attributeIds) {
CategorizedClinicalDataCountFilter categorizedClinicalDataCountFilter = extractClinicalDataCountFilters(studyViewFilter);
return studyViewRepository.getSampleClinicalData(studyViewFilter, attributeIds, categorizedClinicalDataCountFilter);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.cbioportal.web.columnar;

import org.cbioportal.model.AlterationCountByGene;
import org.cbioportal.model.AlterationFilter;
import org.cbioportal.model.ClinicalDataBin;
import org.cbioportal.model.ClinicalDataCountItem;
import org.cbioportal.model.Sample;
import org.cbioportal.service.StudyViewColumnarService;
import org.cbioportal.service.StudyViewService;
import org.cbioportal.service.exception.StudyNotFoundException;
import org.cbioportal.web.columnar.util.NewStudyViewFilterUtil;
import org.cbioportal.web.config.annotation.InternalApi;
import org.cbioportal.web.parameter.ClinicalDataBinCountFilter;
import org.cbioportal.web.parameter.ClinicalDataCountFilter;
import org.cbioportal.web.parameter.ClinicalDataFilter;
import org.cbioportal.web.parameter.DataBinMethod;
import org.cbioportal.web.parameter.StudyViewFilter;
import org.cbioportal.web.util.ClinicalDataBinUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@InternalApi
@RestController()
@RequestMapping("/api")
@Validated
public class StudyViewColumnStoreController {

private final StudyViewColumnarService studyViewColumnarService;
private final StudyViewService studyViewService;
private final ClinicalDataBinUtil clinicalDataBinUtil;

@Autowired
public StudyViewColumnStoreController(StudyViewColumnarService studyViewColumnarService, StudyViewService studyViewService, ClinicalDataBinUtil clinicalDataBinUtil) {
this.studyViewColumnarService = studyViewColumnarService;
this.studyViewService = studyViewService;
this.clinicalDataBinUtil = clinicalDataBinUtil;
}

@PreAuthorize("hasPermission(#involvedCancerStudies, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@PostMapping(value = "/column-store/filtered-samples/fetch",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Sample>> fetchFilteredSamples(
@RequestParam(defaultValue = "false") Boolean negateFilters,
@RequestAttribute(required = false, value = "involvedCancerStudies") Collection<String> involvedCancerStudies,
@RequestAttribute(required = false, value = "interceptedStudyViewFilter") StudyViewFilter interceptedStudyViewFilter,
@RequestBody(required = false) StudyViewFilter studyViewFilter) {
return new ResponseEntity<>(
studyViewColumnarService.getFilteredSamples(interceptedStudyViewFilter),
HttpStatus.OK
);
}

@PreAuthorize("hasPermission(#involvedCancerStudies, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@PostMapping(value = "/column-store/mutated-genes/fetch",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<AlterationCountByGene>> fetchMutatedGenes(
@RequestBody(required = false) StudyViewFilter studyViewFilter,
@RequestAttribute(required = false, value = "involvedCancerStudies") Collection<String> involvedCancerStudies,
@RequestAttribute(required = false, value = "interceptedStudyViewFilter") StudyViewFilter interceptedStudyViewFilter
) throws StudyNotFoundException {
AlterationFilter annotationFilters = interceptedStudyViewFilter.getAlterationFilter();
List<Sample> samples = studyViewColumnarService.getFilteredSamples(interceptedStudyViewFilter);
List<String> studyIds = new ArrayList<>();
List<String> sampleIds = new ArrayList<>();
for(Sample sample : samples) {
studyIds.add(sample.getCancerStudyIdentifier());
sampleIds.add(sample.getStableId());
}
return new ResponseEntity<>(
studyViewService.getMutationAlterationCountByGenes(studyIds, sampleIds, annotationFilters),
HttpStatus.OK
);
}

@PreAuthorize("hasPermission(#involvedCancerStudies, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@PostMapping(value = "/column-store/clinical-data-counts/fetch",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<ClinicalDataCountItem>> fetchClinicalDataCounts(
@RequestBody(required = false) ClinicalDataCountFilter clinicalDataCountFilter,
@RequestAttribute(required = false, value = "involvedCancerStudies") Collection<String> involvedCancerStudies,
@RequestAttribute(required = false, value = "interceptedClinicalDataCountFilter") ClinicalDataCountFilter interceptedClinicalDataCountFilter) {

List<ClinicalDataFilter> attributes = interceptedClinicalDataCountFilter.getAttributes();
StudyViewFilter studyViewFilter = interceptedClinicalDataCountFilter.getStudyViewFilter();

if (attributes.size() == 1) {
NewStudyViewFilterUtil.removeSelfFromFilter(attributes.get(0).getAttributeId(), studyViewFilter);
}
// boolean singleStudyUnfiltered = studyViewFilterUtil.isSingleStudyUnfiltered(studyViewFilter);
List<ClinicalDataCountItem> result = studyViewColumnarService.getClinicalDataCounts(studyViewFilter,
attributes.stream().map(ClinicalDataFilter::getAttributeId).collect(Collectors.toList()));
//studyIds, sampleIds, attributes.stream().map(a -> a.getAttributeId()).collect(Collectors.toList()));
return new ResponseEntity<>(result, HttpStatus.OK);

}

@PreAuthorize("hasPermission(#involvedCancerStudies, 'Collection<CancerStudyId>', T(org.cbioportal.utils.security.AccessLevel).READ)")
@RequestMapping(value = "/column-store/clinical-data-bin-counts/fetch", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<ClinicalDataBin>> fetchClinicalDataBinCounts(
@RequestParam(defaultValue = "DYNAMIC") DataBinMethod dataBinMethod,
@RequestBody(required = false) ClinicalDataBinCountFilter clinicalDataBinCountFilter,
@RequestAttribute(required = false, value = "involvedCancerStudies") Collection<String> involvedCancerStudies,
@RequestAttribute(required = false, value = "interceptedClinicalDataBinCountFilter") ClinicalDataBinCountFilter interceptedClinicalDataBinCountFilter
) {
List<ClinicalDataBin> clinicalDataBins = clinicalDataBinUtil.fetchClinicalDataBinCounts(
dataBinMethod,
interceptedClinicalDataBinCountFilter,
true
);
return new ResponseEntity<>(clinicalDataBins, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.cbioportal.web.columnar.util;


import org.cbioportal.web.parameter.StudyViewFilter;

public class NewStudyViewFilterUtil {

public static void removeSelfFromFilter(String attributeId, StudyViewFilter studyViewFilter) {
if (studyViewFilter!= null && studyViewFilter.getClinicalDataFilters() != null) {
studyViewFilter.getClinicalDataFilters().removeIf(f -> f.getAttributeId().equals(attributeId));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.cbioportal.model.AlterationFilter;
import org.cbioportal.model.MolecularProfile;
import org.cbioportal.model.MolecularProfileCaseIdentifier;
Expand Down Expand Up @@ -140,6 +141,7 @@ public class InvolvedCancerStudyExtractorInterceptor implements HandlerIntercept
// reset this to 'String requestPathInfo = request.getPathInfo();'
String requestPathInfo = request.getPathInfo() == null? request.getServletPath() : request.getPathInfo();
requestPathInfo = requestPathInfo.replaceFirst("^/api", "");
requestPathInfo = StringUtils.removeStart(requestPathInfo, "/column-store");
if (requestPathInfo.equals(PATIENT_FETCH_PATH)) {
return extractAttributesFromPatientFilter(request);
} else if (requestPathInfo.equals(SAMPLE_FETCH_PATH)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.cbioportal.persistence.mybatiscolumnstore.StudyViewMapper">
<mapper namespace="org.cbioportal.persistence.mybatiscolumnar.StudyViewMapper">

<!-- for /filtered-sample/fetch (returns Sample objects) -->
<select id="getFilteredSamples" resultType="org.cbioportal.model.Sample">
Expand Down

0 comments on commit 8b07d3f

Please sign in to comment.