Skip to content

Commit cdbcdf8

Browse files
Merge branch 'master' into feat/request-log-duration-ms
2 parents 5231fc8 + a0c2695 commit cdbcdf8

9 files changed

Lines changed: 527 additions & 56 deletions

File tree

src/main/java/org/cbioportal/application/rest/vcolumnstore/ColumnStoreGenericAssayController.java

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@
99
import io.swagger.v3.oas.annotations.responses.ApiResponse;
1010
import io.swagger.v3.oas.annotations.tags.Tag;
1111
import jakarta.validation.Valid;
12+
import jakarta.validation.constraints.Max;
13+
import jakarta.validation.constraints.Min;
1214
import java.util.Arrays;
1315
import java.util.List;
1416
import org.cbioportal.domain.generic_assay.usecase.GetGenericAssayMetaUseCase;
1517
import org.cbioportal.legacy.model.meta.GenericAssayMeta;
1618
import org.cbioportal.legacy.web.config.PublicApiTags;
1719
import org.cbioportal.legacy.web.config.annotation.PublicApi;
1820
import org.cbioportal.legacy.web.parameter.GenericAssayMetaFilter;
21+
import org.cbioportal.legacy.web.parameter.HeaderKeyConstants;
22+
import org.cbioportal.legacy.web.parameter.PagingConstants;
1923
import org.cbioportal.legacy.web.parameter.Projection;
24+
import org.springframework.http.HttpHeaders;
25+
import org.springframework.http.HttpStatus;
2026
import org.springframework.http.MediaType;
2127
import org.springframework.http.ResponseEntity;
2228
import org.springframework.validation.annotation.Validated;
@@ -26,6 +32,7 @@
2632
import org.springframework.web.bind.annotation.RequestMethod;
2733
import org.springframework.web.bind.annotation.RequestParam;
2834
import org.springframework.web.bind.annotation.RestController;
35+
import org.springframework.web.server.ResponseStatusException;
2936
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
3037

3138
/**
@@ -67,15 +74,44 @@ public ResponseEntity<StreamingResponseBody> fetchGenericAssayMeta(
6774
@Valid
6875
@RequestBody
6976
GenericAssayMetaFilter genericAssayMetaFilter,
77+
@Parameter(description = "Search keyword that applies to stable ID, name, and description")
78+
@RequestParam(required = false)
79+
String searchTerm,
80+
@Parameter(description = "Page size of the result list")
81+
@Max(PagingConstants.MAX_PAGE_SIZE)
82+
@Min(PagingConstants.MIN_PAGE_SIZE)
83+
@RequestParam(required = false)
84+
Integer pageSize,
85+
@Parameter(description = "Page number of the result list")
86+
@Min(PagingConstants.MIN_PAGE_NUMBER)
87+
@RequestParam(required = false)
88+
Integer pageNumber,
7089
@Parameter(description = "Level of detail of the response")
7190
@RequestParam(defaultValue = "SUMMARY")
7291
Projection projection) {
92+
if ((pageSize == null) != (pageNumber == null)) {
93+
throw new ResponseStatusException(
94+
HttpStatus.BAD_REQUEST, "pageSize and pageNumber must both be supplied together");
95+
}
96+
// Only compute a total count when the caller is actually paging; legacy callers that
97+
// just want the full list (e.g. patient view) shouldn't pay for an extra count query.
98+
Integer totalCount =
99+
pageSize != null && pageNumber != null
100+
? getGenericAssayMetaUseCase.count(
101+
genericAssayMetaFilter.getGenericAssayStableIds(),
102+
genericAssayMetaFilter.getMolecularProfileIds(),
103+
projection.name(),
104+
searchTerm)
105+
: null;
73106
List<GenericAssayMeta> result =
74107
getGenericAssayMetaUseCase.execute(
75108
genericAssayMetaFilter.getGenericAssayStableIds(),
76109
genericAssayMetaFilter.getMolecularProfileIds(),
77-
projection.name());
78-
return streamJson(result);
110+
projection.name(),
111+
searchTerm,
112+
pageSize,
113+
pageNumber);
114+
return streamJson(result, totalCount);
79115
}
80116

81117
// PreAuthorize is removed for performance reason
@@ -97,7 +133,8 @@ public ResponseEntity<StreamingResponseBody> getGenericAssayMeta(
97133
Projection projection) {
98134
return streamJson(
99135
getGenericAssayMetaUseCase.execute(
100-
null, Arrays.asList(molecularProfileId), projection.name()));
136+
null, Arrays.asList(molecularProfileId), projection.name()),
137+
null);
101138
}
102139

103140
@RequestMapping(
@@ -118,11 +155,18 @@ public ResponseEntity<StreamingResponseBody> getGenericAssayMetaByStableId(
118155
Projection projection) {
119156
return streamJson(
120157
getGenericAssayMetaUseCase.execute(
121-
Arrays.asList(genericAssayStableId), null, projection.name()));
158+
Arrays.asList(genericAssayStableId), null, projection.name()),
159+
null);
122160
}
123161

124-
private ResponseEntity<StreamingResponseBody> streamJson(List<GenericAssayMeta> data) {
162+
private ResponseEntity<StreamingResponseBody> streamJson(
163+
List<GenericAssayMeta> data, Integer totalCount) {
164+
HttpHeaders headers = new HttpHeaders();
165+
if (totalCount != null) {
166+
headers.add(HeaderKeyConstants.TOTAL_COUNT, totalCount.toString());
167+
}
125168
return ResponseEntity.ok()
169+
.headers(headers)
126170
.contentType(MediaType.APPLICATION_JSON)
127171
.body(outputStream -> objectMapper.writeValue(outputStream, data));
128172
}

src/main/java/org/cbioportal/domain/generic_assay/repository/GenericAssayRepository.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,14 @@ List<GenericAssayDataCountItem> getGenericAssayDataCountsByProfileType(
8181
* @param stableIds the list of entity stable IDs
8282
* @return a list of {@link GenericAssayMeta} with properties pre-populated
8383
*/
84-
List<GenericAssayMeta> getGenericAssayMetaByStableIds(List<String> stableIds);
84+
default List<GenericAssayMeta> getGenericAssayMetaByStableIds(List<String> stableIds) {
85+
return getGenericAssayMetaByStableIds(stableIds, null, null, null);
86+
}
87+
88+
List<GenericAssayMeta> getGenericAssayMetaByStableIds(
89+
List<String> stableIds, String searchTerm, Integer pageSize, Integer offset);
90+
91+
Integer countGenericAssayMetaByStableIds(List<String> stableIds, String searchTerm);
8592

8693
/**
8794
* Retrieves generic assay meta data for entities belonging to the given molecular profile IDs in
@@ -93,6 +100,18 @@ List<GenericAssayDataCountItem> getGenericAssayDataCountsByProfileType(
93100
* @param stableIds optional additional filter; pass {@code null} to return all entities
94101
* @return a list of {@link GenericAssayMeta} with properties pre-populated
95102
*/
103+
default List<GenericAssayMeta> getGenericAssayMetaByProfileIds(
104+
List<String> profileIds, List<String> stableIds) {
105+
return getGenericAssayMetaByProfileIds(profileIds, stableIds, null, null, null);
106+
}
107+
96108
List<GenericAssayMeta> getGenericAssayMetaByProfileIds(
97-
List<String> profileIds, List<String> stableIds);
109+
List<String> profileIds,
110+
List<String> stableIds,
111+
String searchTerm,
112+
Integer pageSize,
113+
Integer offset);
114+
115+
Integer countGenericAssayMetaByProfileIds(
116+
List<String> profileIds, List<String> stableIds, String searchTerm);
98117
}

src/main/java/org/cbioportal/domain/generic_assay/usecase/GetGenericAssayMetaUseCase.java

Lines changed: 119 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.cbioportal.domain.generic_assay.usecase;
22

3+
import java.util.Collection;
34
import java.util.Collections;
45
import java.util.HashSet;
56
import java.util.LinkedHashSet;
67
import java.util.List;
8+
import java.util.Locale;
79
import java.util.Set;
810
import org.cbioportal.domain.generic_assay.repository.GenericAssayRepository;
911
import org.cbioportal.legacy.model.meta.GenericAssayMeta;
@@ -43,6 +45,27 @@ public GetGenericAssayMetaUseCase(GenericAssayRepository repository) {
4345
+ " #projection}")
4446
public List<GenericAssayMeta> execute(
4547
List<String> stableIds, List<String> molecularProfileIds, String projection) {
48+
return execute(stableIds, molecularProfileIds, projection, null, null, null);
49+
}
50+
51+
@Cacheable(
52+
cacheResolver = "generalRepositoryCacheResolver",
53+
condition = "@cacheEnabledConfig.getEnabled()",
54+
key =
55+
"{#stableIds == null ? null : new java.util.TreeSet(#stableIds.?[#this != null]),"
56+
+ " #molecularProfileIds == null ? null : new java.util.TreeSet(#molecularProfileIds.?[#this != null]),"
57+
+ " #projection,"
58+
+ " (#searchTerm == null or #searchTerm.trim().isEmpty() ? null : #searchTerm.trim()),"
59+
+ " #pageSize, #pageNumber}")
60+
public List<GenericAssayMeta> execute(
61+
List<String> stableIds,
62+
List<String> molecularProfileIds,
63+
String projection,
64+
String searchTerm,
65+
Integer pageSize,
66+
Integer pageNumber) {
67+
String normalizedSearchTerm = normalizeSearchTerm(searchTerm);
68+
Integer offset = pageSize == null || pageNumber == null ? null : pageSize * pageNumber;
4669

4770
if (molecularProfileIds != null) {
4871
List<String> sortedProfileIds = molecularProfileIds.stream().distinct().sorted().toList();
@@ -52,16 +75,16 @@ public List<GenericAssayMeta> execute(
5275

5376
if ("ID".equals(projection)) {
5477
// Lightweight path: resolve IDs only, skip meta fetch
55-
Set<String> resolvedIds =
56-
new LinkedHashSet<>(repository.getGenericAssayStableIdsByProfileIds(sortedProfileIds));
57-
if (stableIds != null) {
58-
resolvedIds.retainAll(new HashSet<>(stableIds));
59-
}
60-
return resolvedIds.stream().map(GenericAssayMeta::new).toList();
78+
var filteredIds =
79+
resolveFilteredIdsByProfileIds(sortedProfileIds, stableIds, normalizedSearchTerm);
80+
return pageIds(filteredIds, pageSize, pageNumber).stream()
81+
.map(GenericAssayMeta::new)
82+
.toList();
6183
}
6284

6385
// Single merged query: profile → entity + meta join
64-
return repository.getGenericAssayMetaByProfileIds(sortedProfileIds, stableIds);
86+
return repository.getGenericAssayMetaByProfileIds(
87+
sortedProfileIds, stableIds, normalizedSearchTerm, pageSize, offset);
6588
}
6689

6790
if (stableIds == null || stableIds.isEmpty()) {
@@ -71,9 +94,96 @@ public List<GenericAssayMeta> execute(
7194
List<String> distinctStableIds = stableIds.stream().distinct().toList();
7295

7396
if ("ID".equals(projection)) {
74-
return distinctStableIds.stream().map(GenericAssayMeta::new).toList();
97+
return pageIds(
98+
filterIdsBySearchTerm(distinctStableIds, normalizedSearchTerm), pageSize, pageNumber)
99+
.stream()
100+
.map(GenericAssayMeta::new)
101+
.toList();
102+
}
103+
104+
return repository.getGenericAssayMetaByStableIds(
105+
distinctStableIds, normalizedSearchTerm, pageSize, offset);
106+
}
107+
108+
// Mirrors the ID-projection branches in execute(): count must use the same match
109+
// criteria (stable ID only, no name/description) as the data it's counting, or the
110+
// reported total-count won't line up with what's actually paginable for that projection.
111+
@Cacheable(
112+
cacheResolver = "generalRepositoryCacheResolver",
113+
condition = "@cacheEnabledConfig.getEnabled()",
114+
key =
115+
"{#stableIds == null ? null : new java.util.TreeSet(#stableIds.?[#this != null]),"
116+
+ " #molecularProfileIds == null ? null : new java.util.TreeSet(#molecularProfileIds.?[#this != null]),"
117+
+ " #projection,"
118+
+ " (#searchTerm == null or #searchTerm.trim().isEmpty() ? null : #searchTerm.trim()),"
119+
+ " 'count'}")
120+
public Integer count(
121+
List<String> stableIds,
122+
List<String> molecularProfileIds,
123+
String projection,
124+
String searchTerm) {
125+
String normalizedSearchTerm = normalizeSearchTerm(searchTerm);
126+
127+
if (molecularProfileIds != null) {
128+
List<String> sortedProfileIds = molecularProfileIds.stream().distinct().sorted().toList();
129+
if (sortedProfileIds.isEmpty()) {
130+
return 0;
131+
}
132+
if ("ID".equals(projection)) {
133+
return resolveFilteredIdsByProfileIds(sortedProfileIds, stableIds, normalizedSearchTerm)
134+
.size();
135+
}
136+
return repository.countGenericAssayMetaByProfileIds(
137+
sortedProfileIds, stableIds, normalizedSearchTerm);
138+
}
139+
140+
if (stableIds == null || stableIds.isEmpty()) {
141+
return 0;
142+
}
143+
144+
List<String> distinctStableIds = stableIds.stream().distinct().toList();
145+
if ("ID".equals(projection)) {
146+
return filterIdsBySearchTerm(distinctStableIds, normalizedSearchTerm).size();
147+
}
148+
149+
return repository.countGenericAssayMetaByStableIds(distinctStableIds, normalizedSearchTerm);
150+
}
151+
152+
private String normalizeSearchTerm(String searchTerm) {
153+
if (searchTerm == null || searchTerm.isBlank()) {
154+
return null;
155+
}
156+
return searchTerm.trim();
157+
}
158+
159+
private boolean containsSearchText(String value, String searchTerm) {
160+
return searchTerm == null
161+
|| (value != null
162+
&& value.toLowerCase(Locale.ROOT).contains(searchTerm.toLowerCase(Locale.ROOT)));
163+
}
164+
165+
private List<String> filterIdsBySearchTerm(Collection<String> ids, String normalizedSearchTerm) {
166+
return ids.stream().filter(id -> containsSearchText(id, normalizedSearchTerm)).toList();
167+
}
168+
169+
private List<String> resolveFilteredIdsByProfileIds(
170+
List<String> sortedProfileIds, List<String> stableIds, String normalizedSearchTerm) {
171+
Set<String> resolvedIds =
172+
new LinkedHashSet<>(repository.getGenericAssayStableIdsByProfileIds(sortedProfileIds));
173+
if (stableIds != null) {
174+
resolvedIds.retainAll(new HashSet<>(stableIds));
75175
}
176+
return filterIdsBySearchTerm(resolvedIds, normalizedSearchTerm);
177+
}
76178

77-
return repository.getGenericAssayMetaByStableIds(distinctStableIds);
179+
private List<String> pageIds(List<String> ids, Integer pageSize, Integer pageNumber) {
180+
if (pageSize == null || pageNumber == null) {
181+
return ids;
182+
}
183+
int offset = pageSize * pageNumber;
184+
if (offset >= ids.size()) {
185+
return Collections.emptyList();
186+
}
187+
return ids.subList(offset, Math.min(offset + pageSize, ids.size()));
78188
}
79189
}

src/main/java/org/cbioportal/infrastructure/repository/clickhouse/generic_assay/ClickhouseGenericAssayMapper.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,18 @@ List<GenericAssayDataCountItem> getGenericAssayDataCountsByProfileType(
7979
* @param stableIds the list of entity stable IDs
8080
* @return a list of {@link GenericAssayMeta} with properties pre-populated
8181
*/
82-
List<GenericAssayMeta> getGenericAssayMetaByStableIds(List<String> stableIds);
82+
default List<GenericAssayMeta> getGenericAssayMetaByStableIds(List<String> stableIds) {
83+
return getGenericAssayMetaByStableIds(stableIds, null, null, null);
84+
}
85+
86+
List<GenericAssayMeta> getGenericAssayMetaByStableIds(
87+
@Param("stableIds") List<String> stableIds,
88+
@Param("searchTerm") String searchTerm,
89+
@Param("pageSize") Integer pageSize,
90+
@Param("offset") Integer offset);
91+
92+
Integer countGenericAssayMetaByStableIds(
93+
@Param("stableIds") List<String> stableIds, @Param("searchTerm") String searchTerm);
8394

8495
/**
8596
* Resolves profile IDs → entity stable IDs via generic_assay_profile_entity_derived and joins
@@ -89,6 +100,20 @@ List<GenericAssayDataCountItem> getGenericAssayDataCountsByProfileType(
89100
* @param stableIds optional additional stable ID filter; {@code null} means no filter
90101
* @return a list of {@link GenericAssayMeta} with properties pre-populated
91102
*/
103+
default List<GenericAssayMeta> getGenericAssayMetaByProfileIds(
104+
List<String> profileIds, List<String> stableIds) {
105+
return getGenericAssayMetaByProfileIds(profileIds, stableIds, null, null, null);
106+
}
107+
92108
List<GenericAssayMeta> getGenericAssayMetaByProfileIds(
93-
@Param("profileIds") List<String> profileIds, @Param("stableIds") List<String> stableIds);
109+
@Param("profileIds") List<String> profileIds,
110+
@Param("stableIds") List<String> stableIds,
111+
@Param("searchTerm") String searchTerm,
112+
@Param("pageSize") Integer pageSize,
113+
@Param("offset") Integer offset);
114+
115+
Integer countGenericAssayMetaByProfileIds(
116+
@Param("profileIds") List<String> profileIds,
117+
@Param("stableIds") List<String> stableIds,
118+
@Param("searchTerm") String searchTerm);
94119
}

src/main/java/org/cbioportal/infrastructure/repository/clickhouse/generic_assay/ClickhouseGenericAssayRepository.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,30 @@ public List<String> getGenericAssayStableIdsByProfileIds(List<String> molecularP
5858
}
5959

6060
@Override
61-
public List<GenericAssayMeta> getGenericAssayMetaByStableIds(List<String> stableIds) {
62-
return mapper.getGenericAssayMetaByStableIds(stableIds);
61+
public List<GenericAssayMeta> getGenericAssayMetaByStableIds(
62+
List<String> stableIds, String searchTerm, Integer pageSize, Integer offset) {
63+
return mapper.getGenericAssayMetaByStableIds(stableIds, searchTerm, pageSize, offset);
64+
}
65+
66+
@Override
67+
public Integer countGenericAssayMetaByStableIds(List<String> stableIds, String searchTerm) {
68+
return mapper.countGenericAssayMetaByStableIds(stableIds, searchTerm);
6369
}
6470

6571
@Override
6672
public List<GenericAssayMeta> getGenericAssayMetaByProfileIds(
67-
List<String> profileIds, List<String> stableIds) {
68-
return mapper.getGenericAssayMetaByProfileIds(profileIds, stableIds);
73+
List<String> profileIds,
74+
List<String> stableIds,
75+
String searchTerm,
76+
Integer pageSize,
77+
Integer offset) {
78+
return mapper.getGenericAssayMetaByProfileIds(
79+
profileIds, stableIds, searchTerm, pageSize, offset);
80+
}
81+
82+
@Override
83+
public Integer countGenericAssayMetaByProfileIds(
84+
List<String> profileIds, List<String> stableIds, String searchTerm) {
85+
return mapper.countGenericAssayMetaByProfileIds(profileIds, stableIds, searchTerm);
6986
}
7087
}

0 commit comments

Comments
 (0)