Skip to content

Commit 45bb5c2

Browse files
authored
Merge pull request #232 from overture-stack/rc/4.0.0
Release Candidate 4.0.0
2 parents 6d18621 + 2491de1 commit 45bb5c2

27 files changed

Lines changed: 2955 additions & 56 deletions

.mvn/maven.config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
-Drevision=3.12.0
1+
-Drevision=4.0.0
22
-Dsha1=
3-
-Dchangelist=
3+
-Dchangelist=-SNAPSHOT

maestro-app/src/main/java/bio/overture/maestro/app/infra/adapter/outbound/metadata/study/song/SongStudyDAO.java

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@
2424
import bio.overture.maestro.app.infra.config.properties.ApplicationProperties;
2525
import bio.overture.maestro.domain.api.exception.NotFoundException;
2626
import bio.overture.maestro.domain.entities.metadata.study.Analysis;
27+
import bio.overture.maestro.domain.entities.metadata.study.GetAnalysisResponse;
2728
import bio.overture.maestro.domain.entities.metadata.study.Study;
2829
import bio.overture.maestro.domain.port.outbound.metadata.study.GetAllStudiesCommand;
2930
import bio.overture.maestro.domain.port.outbound.metadata.study.GetAnalysisCommand;
3031
import bio.overture.maestro.domain.port.outbound.metadata.study.GetStudyAnalysesCommand;
3132
import bio.overture.maestro.domain.port.outbound.metadata.study.StudyDAO;
3233
import java.time.Duration;
3334
import java.util.List;
35+
import java.util.concurrent.atomic.AtomicInteger;
3436
import java.util.function.Function;
3537
import javax.inject.Inject;
3638
import lombok.NonNull;
@@ -48,7 +50,7 @@
4850
class SongStudyDAO implements StudyDAO {
4951

5052
private static final String STUDY_ANALYSES_URL_TEMPLATE =
51-
"{0}/studies/{1}/analysis?analysisStates={2}";
53+
"{0}/studies/{1}/analysis/paginated?analysisStates={2}&limit={3}&offset={4}";
5254
private static final String STUDY_ANALYSIS_URL_TEMPLATE = "{0}/studies/{1}/analysis/{2}";
5355
private static final String STUDIES_URL_TEMPLATE = "{0}/studies/all";
5456
private static final String MSG_STUDY_DOES_NOT_EXIST =
@@ -58,6 +60,7 @@ class SongStudyDAO implements StudyDAO {
5860
private static final int FALLBACK_SONG_TIMEOUT = 60;
5961
private static final int FALLBACK_SONG_ANALYSIS_TIMEOUT = 5;
6062
private static final int FALLBACK_SONG_MAX_RETRY = 0;
63+
private static final int DEFAULT_SONG_PAGE_LIMIT = 25;
6164
private final WebClient webClient;
6265
private final int songMaxRetries;
6366
private final int minBackoffSec = 1;
@@ -68,11 +71,16 @@ class SongStudyDAO implements StudyDAO {
6871
private final int studyCallTimeoutSeconds;
6972

7073
private final int analysisCallTimeoutSeconds;
74+
private final int pageLimit;
7175

7276
@Inject
7377
public SongStudyDAO(
7478
@NonNull WebClient webClient, @NonNull ApplicationProperties applicationProperties) {
7579
this.webClient = webClient;
80+
this.pageLimit =
81+
applicationProperties.pageLimit() > 0
82+
? applicationProperties.pageLimit()
83+
: DEFAULT_SONG_PAGE_LIMIT;
7684
this.indexableStudyStatuses = applicationProperties.indexableStudyStatuses();
7785
this.indexableStudyStatusesList = List.of(indexableStudyStatuses.split(","));
7886
this.songMaxRetries =
@@ -94,34 +102,68 @@ public Mono<List<Analysis>> getStudyAnalyses(GetStudyAnalysesCommand getStudyAna
94102
log.trace("in getStudyAnalyses, args: {} ", getStudyAnalysesCommand);
95103
val repoBaseUrl = getStudyAnalysesCommand.getFilesRepositoryBaseUrl();
96104
val studyId = getStudyAnalysesCommand.getStudyId();
97-
val analysisListType = new ParameterizedTypeReference<List<Analysis>>() {};
105+
106+
var initialOffset = 0;
107+
val url =
108+
format(
109+
STUDY_ANALYSES_URL_TEMPLATE,
110+
repoBaseUrl,
111+
studyId,
112+
this.indexableStudyStatuses,
113+
this.pageLimit,
114+
initialOffset);
115+
val threadSafeOffset = new AtomicInteger(0);
116+
117+
return fetchItems(url, studyId)
118+
// The expand method recursively calls fetchItems() and emits response of first page to the
119+
// last.
120+
// the first request being made is offset = 0, and the second request is offset = 25,
121+
// and all the way to the last page.
122+
.expand(
123+
rep -> {
124+
if (rep.getAnalyses().size() == 0) {
125+
return Mono.empty();
126+
}
127+
threadSafeOffset.addAndGet(this.pageLimit);
128+
val currentUrl =
129+
format(
130+
STUDY_ANALYSES_URL_TEMPLATE,
131+
repoBaseUrl,
132+
studyId,
133+
this.indexableStudyStatuses,
134+
this.pageLimit,
135+
threadSafeOffset.get());
136+
return fetchItems(currentUrl, studyId);
137+
})
138+
.flatMap(rep -> Flux.fromIterable(rep.getAnalyses()))
139+
.collectList()
140+
.doOnSuccess(
141+
(list) ->
142+
log.trace(
143+
"getStudyAnalyses out, analyses count {} args: {}",
144+
list.size(),
145+
getStudyAnalysesCommand));
146+
}
147+
148+
private Mono<GetAnalysisResponse> fetchItems(@NonNull String url, @NonNull String studyId) {
149+
log.trace("get paged analyses, url = {}", url);
98150
val retryConfig =
99151
Retry.allBut(NotFoundException.class)
100152
.retryMax(this.songMaxRetries)
101153
.doOnRetry(
102154
retryCtx ->
103-
log.error(
104-
"exception happened, retrying {}",
105-
getStudyAnalysesCommand,
106-
retryCtx.exception()))
155+
log.error("exception happened, retrying {}", url, retryCtx.exception()))
107156
.exponentialBackoff(
108157
Duration.ofSeconds(minBackoffSec), Duration.ofSeconds(maxBackoffSec));
109-
110158
return this.webClient
111159
.get()
112-
.uri(format(STUDY_ANALYSES_URL_TEMPLATE, repoBaseUrl, studyId, this.indexableStudyStatuses))
160+
.uri(url)
113161
.retrieve()
114162
.onStatus(
115163
HttpStatus.NOT_FOUND::equals,
116164
clientResponse -> error(notFound(MSG_STUDY_DOES_NOT_EXIST, studyId)))
117-
.bodyToMono(analysisListType)
118-
.transform(retryAndTimeout(retryConfig, Duration.ofSeconds(this.studyCallTimeoutSeconds)))
119-
.doOnSuccess(
120-
(list) ->
121-
log.trace(
122-
"getStudyAnalyses out, analyses count {} args: {}",
123-
list.size(),
124-
getStudyAnalysesCommand));
165+
.bodyToMono(GetAnalysisResponse.class)
166+
.transform(retryAndTimeout(retryConfig, Duration.ofSeconds(this.studyCallTimeoutSeconds)));
125167
}
126168

127169
@Override

maestro-app/src/main/java/bio/overture/maestro/app/infra/config/properties/ApplicationProperties.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ public interface ApplicationProperties {
8080

8181
String indexableStudyStatuses();
8282

83+
int pageLimit();
84+
8385
int songAnalysisCallTimeoutSeconds();
8486

8587
Slack.SlackChannelInfo getSlackChannelInfo();

maestro-app/src/main/java/bio/overture/maestro/app/infra/config/properties/DefaultApplicationProperties.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ public String indexableStudyStatuses() {
171171
return this.song.getIndexableStudyStatesCsv();
172172
}
173173

174+
@Override
175+
public int pageLimit() {
176+
return this.song.getPageLimit();
177+
}
178+
174179
@Override
175180
public int songAnalysisCallTimeoutSeconds() {
176181
return this.song.getTimeoutSec().getAnalysis();
@@ -270,6 +275,7 @@ private static class Song {
270275
private int maxRetries = 3;
271276
// FIXME: This configuration is called three different things in this codebase
272277
private String indexableStudyStatesCsv = "PUBLISHED";
278+
private int pageLimit = 25;
273279
}
274280

275281
@Data

maestro-app/src/main/resources/config/application.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ maestro:
1515
maxInMemorySize: -1
1616
song:
1717
indexableStudyStatesCsv: PUBLISHED
18+
# page limit for getting analysis from song
19+
pageLimit: 25
1820
maxRetries: 3
1921
timeoutSec:
2022
study: 100 # some studies take really long, +30 secs, to be downloaded

maestro-app/src/test/java/bio/overture/maestro/app/infra/adapter/outbound/metadata/study/song/SongStudyDAOTest.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,30 +135,50 @@ void shouldRetryFetchingAnalysisOnFailure() {
135135
@Test
136136
@SneakyThrows
137137
void shouldRetryFetchingStudyAnalysesOnFailure() {
138-
val analyses = loadJsonString(this.getClass(), "PEME-CA.study.json");
138+
val analyses = loadJsonString(this.getClass(), "PEME-CA.response.json");
139+
val emptyResp = loadJsonString(this.getClass(), "empty-response.json");
139140
val analysesList =
140141
loadJsonFixture(
141142
this.getClass(), "PEME-CA.study.json", new TypeReference<List<Analysis>>() {});
142143
stubFor(
143-
request("GET", urlEqualTo("/studies/PEME-CA/analysis?analysisStates=PUBLISHED"))
144+
request(
145+
"GET",
146+
urlMatching(
147+
"/studies/PEME-CA/analysis/paginated\\?analysisStates=PUBLISHED&limit=25&offset=\\d+"))
144148
.inScenario("RANDOM_FAILURE")
145149
.whenScenarioStateIs(Scenario.STARTED)
146150
.willReturn(
147151
aResponse()
148152
.withStatus(400)
149-
.withBody("<p> some wierd unexpected text </p>")
153+
.withBody("<p> some weird unexpected text </p>")
150154
.withHeader("content-type", "text/html"))
151155
.willSetStateTo("WORKING"));
152156

153157
stubFor(
154-
request("GET", urlEqualTo("/studies/PEME-CA/analysis?analysisStates=PUBLISHED"))
158+
request(
159+
"GET",
160+
urlEqualTo(
161+
"/studies/PEME-CA/analysis/paginated?analysisStates=PUBLISHED&limit=25&offset=0"))
155162
.inScenario("RANDOM_FAILURE")
156163
.whenScenarioStateIs("WORKING")
157164
.willReturn(
158165
aResponse()
159166
.withBody(analyses)
160167
.withStatus(200)
161168
.withHeader("content-type", "application/json")));
169+
// Mock the second/last request:
170+
stubFor(
171+
request(
172+
"GET",
173+
urlEqualTo(
174+
"/studies/PEME-CA/analysis/paginated?analysisStates=PUBLISHED&limit=25&offset=25"))
175+
.inScenario("RANDOM_FAILURE")
176+
.whenScenarioStateIs("WORKING")
177+
.willReturn(
178+
aResponse()
179+
.withBody(emptyResp)
180+
.withStatus(200)
181+
.withHeader("content-type", "application/json")));
162182

163183
val analysesMono =
164184
songStudyDAO.getStudyAnalyses(
@@ -179,11 +199,24 @@ void fetchingStudyAnalysesShouldReturnRetryExhaustedException() {
179199
.studyId("PEME-CA")
180200
.build();
181201
stubFor(
182-
request("GET", urlEqualTo("/studies/PEME-CA/analysis?analysisStates=PUBLISHED"))
202+
request(
203+
"GET",
204+
urlEqualTo(
205+
"/studies/PEME-CA/analysis/paginated?analysisStates=PUBLISHED&limit=25&offset=0"))
183206
.willReturn(
184207
aResponse()
185208
.withStatus(400)
186-
.withBody("<p> Some wierd unexpected text </p>")
209+
.withBody("<p> Some weird unexpected text </p>")
210+
.withHeader("content-type", "text/html")));
211+
stubFor(
212+
request(
213+
"GET",
214+
urlEqualTo(
215+
"/studies/PEME-CA/analysis/paginated?analysisStates=PUBLISHED&limit=25&offset=100"))
216+
.willReturn(
217+
aResponse()
218+
.withStatus(400)
219+
.withBody("<p> Some weird unexpected text </p>")
187220
.withHeader("content-type", "text/html")));
188221

189222
// when

0 commit comments

Comments
 (0)