From 7afd4bd9e7e894506b7521ec5ed9457a539fefc2 Mon Sep 17 00:00:00 2001 From: humsika Date: Wed, 12 Aug 2026 22:39:30 -0400 Subject: [PATCH] BI-2918: Updated StudyDAO layer to remove cache reads and call BrAPI directly. --- .../brapi/v2/dao/BrAPIStudyDAO.java | 53 +++++- .../brapi/v2/dao/BrAPIStudyDAOUnitTest.java | 177 ++++++++++++++++++ 2 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAOUnitTest.java diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAO.java index ccbe0abfd..e228f7e8d 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAO.java @@ -22,8 +22,10 @@ import io.micronaut.scheduling.annotation.Scheduled; import lombok.extern.slf4j.Slf4j; import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.client.v2.model.queryParams.core.StudyQueryParams; import org.brapi.client.v2.modules.core.StudiesApi; import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.core.BrAPIProgram; import org.brapi.v2.model.core.BrAPIStudy; import org.brapi.v2.model.core.request.BrAPIStudySearchRequest; import org.breedinginsight.brapps.importer.daos.ImportDAO; @@ -50,6 +52,8 @@ public class BrAPIStudyDAO extends BrAPICachedDAO { private String referenceSource; @Property(name = "micronaut.bi.api.run-scheduled-tasks") private boolean runScheduledTasks; + @Property(name = "brapi.cache.fetch-page-size") + private int brapiMaxPageSize; private ProgramDAO programDAO; private ImportDAO importDAO; @@ -112,7 +116,39 @@ private Map fetchProgramStudy(UUID programId) throws ApiExce * @throws ApiException */ public List getStudies(UUID programId) throws ApiException { - return new ArrayList<>(programCache.get(programId).values()); + Program program = programDAO.get(programId) + .stream() + .findFirst() + .orElseThrow(); + + return getBrAPIStudiesUsingBrAPIProgramId(program); + } + + private List getBrAPIStudiesUsingBrAPIProgramId(Program program) throws ApiException { + if (program == null || program.getId() == null) { + throw new InternalServerException("BI-API Program or Program ID is null"); + } + + String brapiProgramDbId = Optional.of(program) + .map(Program::getBrapiProgram) + .map(BrAPIProgram::getProgramDbId) + .orElse(null); + + if (brapiProgramDbId == null) { + brapiProgramDbId = programDAO.getProgramBrAPI(program).getProgramDbId(); + } + + StudyQueryParams studyQueryParams = StudyQueryParams.builder() + .programDbId(brapiProgramDbId) + .page(0) + .pageSize(brapiMaxPageSize) + .build(); + + StudiesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), StudiesApi.class); + + List result = brAPIDAOUtil.get(api::studiesGet, studyQueryParams); + + return new ArrayList<>(processStudyForDisplay(result, program.getKey()).values()); } public Optional getStudyByName(String studyName, Program program) throws ApiException { @@ -151,12 +187,13 @@ public List getStudiesByBrAPITrialExRefId(@NotNull UUID brapiTrialEx } public List getStudiesByEnvironmentIds(@NotNull Collection environmentIds, Program program) throws ApiException { - return programCache.get(program.getId()) - .entrySet() - .stream() - .filter(entry -> environmentIds.contains(UUID.fromString(entry.getKey()))) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); + String refSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES); + + return getBrAPIStudiesUsingBrAPIProgramId(program).stream() + .filter(study -> Utilities.getExternalReference(study.getExternalReferences(), refSource) + .map(ref -> environmentIds.contains(UUID.fromString(ref.getReferenceId()))) + .orElse(false)) + .collect(Collectors.toList()); } /** @@ -275,7 +312,7 @@ private Map processStudyForDisplay(List programSt BrAPIExternalReference extRef = study.getExternalReferences().stream() .filter(reference -> reference.getReferenceSource().equals(refSource)) .findFirst().orElseThrow(() -> new IllegalStateException("No BI external reference found")); - String studyId = extRef.getReferenceID(); + String studyId = extRef.getReferenceId(); programStudyMap.put(studyId, study); } diff --git a/src/test/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAOUnitTest.java b/src/test/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAOUnitTest.java new file mode 100644 index 000000000..90b8d90b1 --- /dev/null +++ b/src/test/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAOUnitTest.java @@ -0,0 +1,177 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.breedinginsight.brapi.v2.dao; + +import io.reactivex.functions.Function; +import lombok.SneakyThrows; +import org.brapi.client.v2.BrAPIClient; +import org.brapi.client.v2.model.queryParams.core.StudyQueryParams; +import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.core.BrAPIProgram; +import org.brapi.v2.model.core.BrAPIStudy; +import org.breedinginsight.brapps.importer.daos.ImportDAO; +import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; +import org.breedinginsight.daos.ProgramDAO; +import org.breedinginsight.daos.cache.FetchFunction; +import org.breedinginsight.daos.cache.ProgramCache; +import org.breedinginsight.daos.cache.ProgramCacheProvider; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.brapi.BrAPIEndpointProvider; +import org.breedinginsight.utilities.BrAPIDAOUtil; +import org.breedinginsight.utilities.Utilities; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +public class BrAPIStudyDAOUnitTest { + + private static final String REFERENCE_SOURCE = "breedinginsight.org"; + + private BrAPIStudyDAO studyDAO; + private ProgramDAO programDAO; + private BrAPIDAOUtil brAPIDAOUtil; + private ProgramCache programCache; + private Program program; + private UUID programId; + private UUID environmentId; + + @BeforeEach + @SneakyThrows + void setup() { + programId = UUID.randomUUID(); + environmentId = UUID.randomUUID(); + + program = new Program(); + program.setId(programId); + program.setKey("TEST"); + BrAPIProgram brapiProgram = new BrAPIProgram() + .programDbId("brapi-program-1"); + program.setBrapiProgram(brapiProgram); + + programDAO = mock(ProgramDAO.class); + brAPIDAOUtil = mock(BrAPIDAOUtil.class); + ProgramCacheProvider programCacheProvider = mock(ProgramCacheProvider.class); + programCache = mock(ProgramCache.class); + + when(programCacheProvider.getProgramCache(any(FetchFunction.class), eq(BrAPIStudy.class))) + .thenReturn(programCache); + when(programDAO.get(programId)).thenReturn(List.of(program)); + when(programDAO.getCoreClient(programId)).thenReturn(mock(BrAPIClient.class)); + when(programDAO.getProgramBrAPI(program)).thenReturn(brapiProgram); + + studyDAO = new BrAPIStudyDAO( + programDAO, + mock(ImportDAO.class), + brAPIDAOUtil, + new BrAPIEndpointProvider(), + programCacheProvider + ); + + Field referenceSource = BrAPIStudyDAO.class.getDeclaredField("referenceSource"); + referenceSource.setAccessible(true); + referenceSource.set(studyDAO, REFERENCE_SOURCE); + + Field brapiMaxPageSize = BrAPIStudyDAO.class.getDeclaredField("brapiMaxPageSize"); + brapiMaxPageSize.setAccessible(true); + brapiMaxPageSize.set(studyDAO, 1000); + } + + @Test + @SneakyThrows + void getStudiesUsesDirectBrAPIGetInsteadOfProgramCache() { + BrAPIStudy study = study(environmentId, "Env1 [TEST-1]"); + + when(brAPIDAOUtil.get(any(Function.class), any(StudyQueryParams.class))) + .thenReturn(List.of(study)); + + List result = studyDAO.getStudies(programId); + + assertEquals(1, result.size()); + assertEquals("Env1", result.get(0).getStudyName()); + ArgumentCaptor queryParamsCaptor = ArgumentCaptor.forClass(StudyQueryParams.class); + verify(brAPIDAOUtil).get(any(Function.class), queryParamsCaptor.capture()); + assertEquals("brapi-program-1", queryParamsCaptor.getValue().programDbId()); + assertEquals(0, queryParamsCaptor.getValue().page()); + assertEquals(1000, queryParamsCaptor.getValue().pageSize()); + verify(programCache, never()).get(any(UUID.class)); + } + + @Test + @SneakyThrows + void getStudiesByEnvironmentIdsUsesDirectBrAPIGetInsteadOfProgramCache() { + BrAPIStudy matchingStudy = study(environmentId, "Env1 [TEST-1]"); + BrAPIStudy otherStudy = study(UUID.randomUUID(), "Env2 [TEST-2]"); + + when(brAPIDAOUtil.get(any(Function.class), any(StudyQueryParams.class))) + .thenReturn(List.of(matchingStudy, otherStudy)); + + List result = studyDAO.getStudiesByEnvironmentIds(List.of(environmentId), program); + + assertEquals(1, result.size()); + assertEquals("Env1", result.get(0).getStudyName()); + ArgumentCaptor queryParamsCaptor = ArgumentCaptor.forClass(StudyQueryParams.class); + verify(brAPIDAOUtil).get(any(Function.class), queryParamsCaptor.capture()); + assertEquals("brapi-program-1", queryParamsCaptor.getValue().programDbId()); + assertEquals(0, queryParamsCaptor.getValue().page()); + assertEquals(1000, queryParamsCaptor.getValue().pageSize()); + verify(programCache, never()).get(any(UUID.class)); + } + + @Test + @SneakyThrows + void getStudiesByEnvironmentIdsReturnsEmptyListWhenStudyIsNotFoundInBrAPI() { + when(brAPIDAOUtil.get(any(Function.class), any(StudyQueryParams.class))) + .thenReturn(List.of()); + + List result = studyDAO.getStudiesByEnvironmentIds(List.of(environmentId), program); + + assertEquals(0, result.size()); + verify(programCache, never()).get(any(UUID.class)); + } + + @Test + @SneakyThrows + void getStudiesByEnvironmentIdsReturnsEmptyListWhenNoStudyExternalReferenceMatches() { + BrAPIStudy study = study(UUID.randomUUID(), "Env2 [TEST-2]"); + + when(brAPIDAOUtil.get(any(Function.class), any(StudyQueryParams.class))) + .thenReturn(List.of(study)); + + List result = studyDAO.getStudiesByEnvironmentIds(List.of(environmentId), program); + + assertEquals(0, result.size()); + verify(programCache, never()).get(any(UUID.class)); + } + + private BrAPIStudy study(UUID environmentId, String studyName) { + BrAPIExternalReference studyReference = new BrAPIExternalReference() + .referenceSource(Utilities.generateReferenceSource(REFERENCE_SOURCE, ExternalReferenceSource.STUDIES)) + .referenceId(environmentId.toString()); + + return new BrAPIStudy() + .studyName(studyName) + .externalReferences(List.of(studyReference)); + } +}