From e00c5aadb2a9a923599f1e1c15bbd70085924ea6 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Thu, 29 Oct 2026 13:53:10 -0400 Subject: [PATCH 1/7] Preliminary exref swapping work on traits and observation variable dbIds --- .../BrAPIObservationVariableController.java | 18 +++-------------- .../brapi/v2/dao/BrAPIObservationDAO.java | 18 ----------------- .../v2/dao/BrAPIObservationVariableDAO.java | 16 +++++++++++++++ .../BrAPIObservationVariableService.java | 20 +++++++++++++++---- 4 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java index 9727ad1be..fb183b706 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java @@ -150,27 +150,15 @@ public HttpResponse variablesGet(@PathVari public HttpResponse variablesObservationVariableDbIdGet(@PathVariable("programId") UUID programId, @PathVariable("observationVariableDbId") String observationVariableDbId) { log.debug("fetching variable: " + observationVariableDbId); - UUID traitId; - try { - traitId = UUID.fromString(observationVariableDbId); - } catch (IllegalArgumentException e) { - return HttpResponse.notFound(); - } try { - Optional trait = traitService.getById(programId, traitId); - - if(trait.isEmpty()) { - return HttpResponse.notFound(); - } - - BrAPIObservationVariableSingleResponse response = new BrAPIObservationVariableSingleResponse() - .metadata(new BrAPIMetadata()) - .result(observationVariableService.convertToBrAPI(trait.get())); + BrAPIObservationVariableSingleResponse response = observationVariableService.getBrAPIObservationVariableByDbId(observationVariableDbId, programId); return HttpResponse.ok(response); } catch (DoesNotExistException e) { return HttpResponse.notFound(); + } catch (ApiException e) { + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "error fetching variables"); } } diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index 0ca219012..3f176028d 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -98,15 +98,6 @@ private void processObservations(String programKey, List obser } } - private List getProgramObservations(UUID programId) throws ApiException { - Program program = programDAO.get(programId) - .stream() - .findFirst() - .orElseThrow(); - - return getBrAPIObservationsUsingBrAPIProgramId(program); - } - public List getObservationsByStudyName(List studyNames, Program program) throws ApiException { if(studyNames.isEmpty()) { return Collections.emptyList(); @@ -220,15 +211,6 @@ public BrAPIObservationListResponse getObservationsByFilters(Program program, St List observations = brAPIDAOUtil.getListResult(brAPIResponse); processObservations(program.getKey(), observations); - // TODO: Investigate if this resetting of the observationVariableDbId can be undone when trait exrefs are examined [BI-2925] - HashMap traitIdsByObservationVariableDbId = traitService.getIdsByObservationVariableDbIds( - program.getId(), observations.stream() - .map(BrAPIObservation::getObservationVariableDbId).collect(Collectors.toList())); - - observations.forEach(observation -> observation - .setObservationVariableDbId(traitIdsByObservationVariableDbId.get(observation.getObservationVariableDbId()))); - brAPIResponse.getResult().setData(observations); - return brAPIResponse; } diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java index 0de5c19ed..892a73cbf 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java @@ -16,10 +16,14 @@ */ package org.breedinginsight.brapi.v2.dao; +import io.micronaut.http.server.exceptions.InternalServerException; +import org.brapi.client.v2.ApiResponse; import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.client.v2.model.queryParams.phenotype.VariableQueryParams; import org.brapi.client.v2.modules.phenotype.ObservationVariablesApi; import org.brapi.v2.model.pheno.BrAPIObservationVariable; import org.brapi.v2.model.pheno.request.BrAPIObservationVariableSearchRequest; +import org.brapi.v2.model.pheno.response.BrAPIObservationVariableSingleResponse; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; import org.breedinginsight.utilities.BrAPIDAOUtil; @@ -59,4 +63,16 @@ public List getVariableByName(List variableNam ); } + public BrAPIObservationVariableSingleResponse getVariableByIdWithResponse(String observationVariableDbId, UUID programId) throws ApiException { + ObservationVariablesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationVariablesApi.class); + + ApiResponse apiResponse = api.variablesObservationVariableDbIdGet(observationVariableDbId); + + if (apiResponse.getBody() != null && apiResponse.getStatusCode() == 200) { + return apiResponse.getBody(); + } + + throw new InternalServerException(String.format("Unable to retrieve BrAPI Observation Variable using id [%s] for BI Program with id: [%s]", observationVariableDbId, programId)); + } + } diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java index 4b579c0f1..da9f1ba2e 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java @@ -25,7 +25,9 @@ import org.brapi.v2.model.core.BrAPIStudy; import org.brapi.v2.model.core.BrAPITrial; import org.brapi.v2.model.pheno.*; +import org.brapi.v2.model.pheno.response.BrAPIObservationVariableSingleResponse; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationVariableDAO; import org.breedinginsight.model.Program; import org.breedinginsight.model.Trait; import org.breedinginsight.services.ProgramService; @@ -43,12 +45,14 @@ public class BrAPIObservationVariableService { private final ProgramService programService; private final BrAPITrialService trialService; + private final BrAPIObservationVariableDAO observationVariableDAO; @Inject public BrAPIObservationVariableService( - ProgramService programService, BrAPITrialService trialService){ + ProgramService programService, BrAPITrialService trialService, BrAPIObservationVariableDAO observationVariableDAO){ this.programService = programService; this.trialService = trialService; + this.observationVariableDAO = observationVariableDAO; } // TODO: support sub-entity datasets. @@ -115,8 +119,7 @@ public List filterVariables(List programTraits, Map, String>> filterParams = new HashMap<>(); filterParams.put("observationVariableDbId", Pair.of(observationVariableDbId, - trait.getId() - .toString())); + trait.getObservationVariableDbId())); filterParams.put("observationVariableName", Pair.of(observationVariableName, trait.getObservationVariableName())); filterParams.put("traitClass", Pair.of(traitClass, trait.getTraitClass())); filterParams.put("methodDbId", @@ -171,7 +174,7 @@ public BrAPIObservationVariable convertToBrAPI(Trait trait) { .toString()); String status = trait.getActive() ? "active" : "inactive"; List synonyms = prepSynonyms(trait); - return new BrAPIObservationVariable().observationVariableDbId(trait.getId().toString()) + return new BrAPIObservationVariable().observationVariableDbId(trait.getObservationVariableDbId()) .observationVariableName(trait.getObservationVariableName()) .defaultValue(trait.getDefaultValue()) .status(status) @@ -227,4 +230,13 @@ private List prepSynonyms(Trait trait) { } return preppedSynonyms; } + + public BrAPIObservationVariableSingleResponse getBrAPIObservationVariableByDbId(String observationVariableDbId, UUID programId) throws ApiException, DoesNotExistException { + + if (!programService.exists(programId)) { + throw new DoesNotExistException("Program does not exist"); + } + + return observationVariableDAO.getVariableByIdWithResponse(observationVariableDbId, programId); + } } From f38827e0881be938178c5fb8546ed088c62eba0e Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Thu, 29 Oct 2026 16:39:36 -0400 Subject: [PATCH 2/7] Fix unit test failures, add processing for ObservationVariables to strip keys --- .../v2/dao/BrAPIObservationVariableDAO.java | 24 +++++++++++++++---- .../BrAPIObservationVariableService.java | 5 +++- ...tionVariableControllerIntegrationTest.java | 12 +++++++--- .../v2/dao/BrAPIObservationDAOUnitTest.java | 2 +- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java index 892a73cbf..2e904777e 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java @@ -25,8 +25,10 @@ import org.brapi.v2.model.pheno.request.BrAPIObservationVariableSearchRequest; import org.brapi.v2.model.pheno.response.BrAPIObservationVariableSingleResponse; import org.breedinginsight.daos.ProgramDAO; +import org.breedinginsight.model.Program; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; import org.breedinginsight.utilities.BrAPIDAOUtil; +import org.breedinginsight.utilities.Utilities; import javax.inject.Inject; import javax.inject.Singleton; @@ -63,16 +65,30 @@ public List getVariableByName(List variableNam ); } - public BrAPIObservationVariableSingleResponse getVariableByIdWithResponse(String observationVariableDbId, UUID programId) throws ApiException { - ObservationVariablesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationVariablesApi.class); + public BrAPIObservationVariableSingleResponse getVariableByIdWithResponse(String observationVariableDbId, Program program) throws ApiException { + ObservationVariablesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), ObservationVariablesApi.class); ApiResponse apiResponse = api.variablesObservationVariableDbIdGet(observationVariableDbId); - if (apiResponse.getBody() != null && apiResponse.getStatusCode() == 200) { + if (apiResponse.getBody() != null && apiResponse.getBody().getResult() != null && apiResponse.getStatusCode() == 200) { + BrAPIObservationVariable returnedObsVar = apiResponse.getBody().getResult(); + + processObservationVariables(List.of(returnedObsVar), program); + return apiResponse.getBody(); } - throw new InternalServerException(String.format("Unable to retrieve BrAPI Observation Variable using id [%s] for BI Program with id: [%s]", observationVariableDbId, programId)); + throw new InternalServerException(String.format("Unable to retrieve BrAPI Observation Variable using id [%s] for BI Program with id: [%s]", observationVariableDbId, program.getId())); + } + + private void processObservationVariables(List observationVariables, Program program) { + observationVariables.forEach(observationVariable -> { + observationVariable.setObservationVariableName(Utilities.removeProgramKey(observationVariable.getObservationVariableName(), program.getKey())); + + if (observationVariable.getScale() != null && observationVariable.getScale().getScaleName() != null) { + observationVariable.getScale().setScaleName(Utilities.removeProgramKey(observationVariable.getScale().getScaleName(), program.getKey())); + } + }); } } diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java index da9f1ba2e..eb0470e12 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java @@ -237,6 +237,9 @@ public BrAPIObservationVariableSingleResponse getBrAPIObservationVariableByDbId( throw new DoesNotExistException("Program does not exist"); } - return observationVariableDAO.getVariableByIdWithResponse(observationVariableDbId, programId); + // We know the program exists as of check above + Program program = programService.getById(programId).get(); + + return observationVariableDAO.getVariableByIdWithResponse(observationVariableDbId, program); } } diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ObservationVariableControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ObservationVariableControllerIntegrationTest.java index 36f739c6d..9f4419bee 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ObservationVariableControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ObservationVariableControllerIntegrationTest.java @@ -161,12 +161,17 @@ void setup() throws Exception { traits = createTraits(4); AuthenticatedUser user = new AuthenticatedUser(testUser.getName(), new ArrayList<>(), testUser.getId(), new ArrayList<>()); try { - ontologyService.createTraits(program.getId(), traits, user, false); + // Re-assign to get obtain all brapi related data as well + traits = ontologyService.createTraits(program.getId(), traits, user, false); } catch (ValidatorException e) { System.err.println(e.getErrors()); throw e; } + // Re-assignment returns the data in an indeterminate order. Sort on variable name, which should be determinate. + // Tests below depend on the order being correct. + traits.sort(Comparator.comparing(Trait::getObservationVariableName)); + // Add germplasm to program List germplasm = createGermplasm(1, brapiProgramDbId); BrAPIExternalReference newReference = new BrAPIExternalReference(); @@ -305,7 +310,7 @@ public void testGetVariablesByExpId() { @Test public void testGetVariableById() { Flowable> call = client.exchange( - GET(String.format("/programs/%s/brapi/v2/variables/%s", program.getId(), traits.get(0).getId())) + GET(String.format("/programs/%s/brapi/v2/variables/%s", program.getId(), traits.get(0).getObservationVariableDbId())) .bearerAuth("test-registered-user"), String.class ); @@ -316,7 +321,7 @@ public void testGetVariableById() { JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); JsonObject variable = responseObj.getAsJsonObject("result"); assertNotNull(variable); - assertEquals(traits.get(0).getId().toString(), variable.get("observationVariableDbId").getAsString()); + assertEquals(traits.get(0).getObservationVariableDbId(), variable.get("observationVariableDbId").getAsString()); assertEquals(traits.get(0).getObservationVariableName(), variable.get("observationVariableName").getAsString()); JsonObject trait = variable.getAsJsonObject("trait"); @@ -404,6 +409,7 @@ private List createTraits(int numToCreate) { String varName = "tt_test_" + (i + 1); traits.add(Trait.builder() .observationVariableName(varName) + .observationVariableDbId(UUID.randomUUID().toString()) .fullName(varName) .entity("Plant " + i) .attribute("height " + i) diff --git a/src/test/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAOUnitTest.java b/src/test/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAOUnitTest.java index 566fce142..ab4cbd794 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAOUnitTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAOUnitTest.java @@ -269,6 +269,6 @@ void getObservationsByFiltersUsesBrAPISearchPagination() { assertEquals(List.of("study-1"), request.getStudyDbIds()); assertEquals(2, request.getPage()); assertEquals(25, request.getPageSize()); - assertEquals(traitId, result.getResult().getData().get(0).getObservationVariableDbId()); + assertEquals(brapiVariableDbId, result.getResult().getData().get(0).getObservationVariableDbId()); } } \ No newline at end of file From d453287bd8969f8ffe0158ae7e5f4edf97c74241 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Thu, 29 Oct 2026 17:16:41 -0400 Subject: [PATCH 3/7] Remove programKeys from other observation variable attributes --- .../brapi/v2/dao/BrAPIObservationVariableDAO.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java index 2e904777e..617ea05a4 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java @@ -88,6 +88,14 @@ private void processObservationVariables(List observat if (observationVariable.getScale() != null && observationVariable.getScale().getScaleName() != null) { observationVariable.getScale().setScaleName(Utilities.removeProgramKey(observationVariable.getScale().getScaleName(), program.getKey())); } + + if (observationVariable.getMethod() != null && observationVariable.getMethod().getMethodName() != null) { + observationVariable.getMethod().setMethodName(Utilities.removeProgramKey(observationVariable.getMethod().getMethodName(), program.getKey())); + } + + if (observationVariable.getTrait() != null && observationVariable.getTrait().getTraitName() != null) { + observationVariable.getTrait().setTraitName(Utilities.removeProgramKey(observationVariable.getTrait().getTraitName(), program.getKey())); + } }); } From 6036dc510d9c2f9971c96a4cdccfcb8b82ca9c26 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Mon, 2 Nov 2026 17:42:06 -0500 Subject: [PATCH 4/7] Address 404 use cases, fix program key stripping --- .../v2/dao/BrAPIObservationVariableDAO.java | 32 ++++++++++++++----- .../BrAPIObservationVariableService.java | 11 +++++-- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java index 617ea05a4..47d9ad165 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java @@ -19,7 +19,6 @@ import io.micronaut.http.server.exceptions.InternalServerException; import org.brapi.client.v2.ApiResponse; import org.brapi.client.v2.model.exceptions.ApiException; -import org.brapi.client.v2.model.queryParams.phenotype.VariableQueryParams; import org.brapi.client.v2.modules.phenotype.ObservationVariablesApi; import org.brapi.v2.model.pheno.BrAPIObservationVariable; import org.brapi.v2.model.pheno.request.BrAPIObservationVariableSearchRequest; @@ -27,6 +26,7 @@ import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.model.Program; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; +import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.utilities.BrAPIDAOUtil; import org.breedinginsight.utilities.Utilities; @@ -65,25 +65,41 @@ public List getVariableByName(List variableNam ); } - public BrAPIObservationVariableSingleResponse getVariableByIdWithResponse(String observationVariableDbId, Program program) throws ApiException { + public BrAPIObservationVariableSingleResponse getVariableByIdWithResponse(String observationVariableDbId, Program program) throws DoesNotExistException { ObservationVariablesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), ObservationVariablesApi.class); - ApiResponse apiResponse = api.variablesObservationVariableDbIdGet(observationVariableDbId); + try { + ApiResponse apiResponse = api.variablesObservationVariableDbIdGet(observationVariableDbId); - if (apiResponse.getBody() != null && apiResponse.getBody().getResult() != null && apiResponse.getStatusCode() == 200) { - BrAPIObservationVariable returnedObsVar = apiResponse.getBody().getResult(); + String brapiProgramDbId = brAPIDAOUtil.getBrAPIProgramDbId(program.getId()); - processObservationVariables(List.of(returnedObsVar), program); + if (apiResponse.getBody() != null && apiResponse.getBody().getResult() != null && apiResponse.getStatusCode() == 200) { + BrAPIObservationVariable returnedObsVar = apiResponse.getBody().getResult(); - return apiResponse.getBody(); + if (returnedObsVar.getProgramDbId() == null || !returnedObsVar.getProgramDbId().equals(brapiProgramDbId)) { + throw new DoesNotExistException(String.format("A observation variable with id [%s] was found but it does not belong to the submitted program with id: [%s]", observationVariableDbId, program.getId())); + } + + processObservationVariables(List.of(returnedObsVar), program); + + return apiResponse.getBody(); + } + } catch (ApiException e) { + if (e.getCode() >= 400 && e.getCode() < 500) { + throw new DoesNotExistException(String.format("Observation variable with brapi db id [%s] was not found for program [%s]", observationVariableDbId, program.getId())); + } } throw new InternalServerException(String.format("Unable to retrieve BrAPI Observation Variable using id [%s] for BI Program with id: [%s]", observationVariableDbId, program.getId())); } private void processObservationVariables(List observationVariables, Program program) { + observationVariables.forEach(observationVariable -> { - observationVariable.setObservationVariableName(Utilities.removeProgramKey(observationVariable.getObservationVariableName(), program.getKey())); + + if (observationVariable.getObservationVariableName() != null) { + observationVariable.setObservationVariableName(Utilities.removeProgramKey(observationVariable.getObservationVariableName(), program.getKey())); + } if (observationVariable.getScale() != null && observationVariable.getScale().getScaleName() != null) { observationVariable.getScale().setScaleName(Utilities.removeProgramKey(observationVariable.getScale().getScaleName(), program.getKey())); diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java index eb0470e12..1120c72de 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java @@ -30,6 +30,7 @@ import org.breedinginsight.brapi.v2.dao.BrAPIObservationVariableDAO; import org.breedinginsight.model.Program; import org.breedinginsight.model.Trait; +import org.breedinginsight.services.OntologyService; import org.breedinginsight.services.ProgramService; import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.utilities.DatasetUtil; @@ -46,13 +47,15 @@ public class BrAPIObservationVariableService { private final ProgramService programService; private final BrAPITrialService trialService; private final BrAPIObservationVariableDAO observationVariableDAO; + private final OntologyService ontologyService; @Inject public BrAPIObservationVariableService( - ProgramService programService, BrAPITrialService trialService, BrAPIObservationVariableDAO observationVariableDAO){ + ProgramService programService, BrAPITrialService trialService, BrAPIObservationVariableDAO observationVariableDAO, OntologyService ontologyService){ this.programService = programService; this.trialService = trialService; this.observationVariableDAO = observationVariableDAO; + this.ontologyService = ontologyService; } // TODO: support sub-entity datasets. @@ -237,8 +240,10 @@ public BrAPIObservationVariableSingleResponse getBrAPIObservationVariableByDbId( throw new DoesNotExistException("Program does not exist"); } - // We know the program exists as of check above - Program program = programService.getById(programId).get(); + // This helper method retrieves the proper program that will be associated with the brapi observation variable that is being looked up. + UUID ontologyProgramLookupId = ontologyService.getSubscribedOntologyProgramId(programId); + + Program program = programService.getById(ontologyProgramLookupId).get(); return observationVariableDAO.getVariableByIdWithResponse(observationVariableDbId, program); } From d5be47a3c12783c92d2cb14e757253cde3407382 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Tue, 3 Nov 2026 12:45:26 -0500 Subject: [PATCH 5/7] Return brapiDbIds for rest of entities in BrAPIObservationVariableController GET endpoints --- .../services/BrAPIObservationVariableService.java | 15 ++++++--------- .../java/org/breedinginsight/model/Method.java | 5 +++++ .../java/org/breedinginsight/model/Scale.java | 5 +++++ .../java/org/breedinginsight/model/Trait.java | 6 ++++-- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java index 1120c72de..66c4adde9 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java @@ -127,8 +127,7 @@ public List filterVariables(List programTraits, filterParams.put("traitClass", Pair.of(traitClass, trait.getTraitClass())); filterParams.put("methodDbId", Pair.of(methodDbId, - trait.getMethodId() - .toString())); + trait.getMethod().getBrapiMethodDbId())); filterParams.put("methodName", Pair.of(methodName, trait.getMethod() @@ -136,16 +135,14 @@ public List filterVariables(List programTraits, filterParams.put("scaleDbId", Pair.of(scaleDbId, trait.getScale() - .getId() - .toString())); + .getBrapiScaleDbId())); filterParams.put("scaleName", Pair.of(scaleName, trait.getScale() .getScaleName())); filterParams.put("traitDbId", Pair.of(traitDbId, - trait.getId() - .toString())); + trait.getBrapiTraitDbId())); filterParams.put("traitName", Pair.of(traitName, trait.getObservationVariableName())); filterParams.put("ontologyDbId", Pair.of(ontologyDbId, @@ -184,18 +181,18 @@ public BrAPIObservationVariable convertToBrAPI(Trait trait) { .synonyms(synonyms) .trait(new BrAPITrait().ontologyReference(brAPIOntologyReference) .traitName(trait.getObservationVariableName()) - .traitDbId(trait.getId().toString()) + .traitDbId(trait.getBrapiTraitDbId()) .entity(trait.getEntity()) .attribute(trait.getAttribute()) .status(status) .synonyms(synonyms)) .method(new BrAPIMethod().ontologyReference(brAPIOntologyReference) - .methodDbId(trait.getMethod().getId().toString()) + .methodDbId(trait.getMethod().getBrapiMethodDbId()) .methodClass(trait.getMethod().getMethodClass()) .description(trait.getMethod().getDescription()) .formula(trait.getMethod().getFormula())) .scale(new BrAPIScale().ontologyReference(brAPIOntologyReference) - .scaleDbId(trait.getScale().getId().toString()) + .scaleDbId(trait.getScale().getBrapiScaleDbId()) .scaleName(trait.getScale().getScaleName()) .dataType(BrAPITraitDataType.fromValue(trait.getScale().getDataType().getLiteral())) .decimalPlaces(trait.getScale().getDecimalPlaces()) diff --git a/src/main/java/org/breedinginsight/model/Method.java b/src/main/java/org/breedinginsight/model/Method.java index b300e96f5..4ce50dd90 100644 --- a/src/main/java/org/breedinginsight/model/Method.java +++ b/src/main/java/org/breedinginsight/model/Method.java @@ -17,6 +17,7 @@ package org.breedinginsight.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.micronaut.core.annotation.Introspected; import lombok.Getter; @@ -48,6 +49,9 @@ public class Method extends MethodEntity { private ProgramOntology programOntology; // BrAPI Properties + @JsonIgnore + // brapiMethodDbId is only used to convert the bi method id to brapiMethodDbId for the BrAPIObservationVariableController GET endpoints + private String brapiMethodDbId; private String methodClass; private String description; private String formula; @@ -76,5 +80,6 @@ public void setBrAPIProperties(BrAPIMethod brApiMethod){ this.setMethodClass(brApiMethod.getMethodClass()); this.setDescription(brApiMethod.getDescription()); this.setFormula(brApiMethod.getFormula()); + this.setBrapiMethodDbId(brApiMethod.getMethodDbId()); } } diff --git a/src/main/java/org/breedinginsight/model/Scale.java b/src/main/java/org/breedinginsight/model/Scale.java index b2458e80b..e504ce6c0 100644 --- a/src/main/java/org/breedinginsight/model/Scale.java +++ b/src/main/java/org/breedinginsight/model/Scale.java @@ -17,6 +17,7 @@ package org.breedinginsight.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.micronaut.core.annotation.Introspected; import lombok.Getter; @@ -50,6 +51,9 @@ public class Scale extends ScaleEntity { private ProgramOntology programOntology; // BrAPI properties + @JsonIgnore + // brapiScaleDbId is only used to convert the bi scale id to brapiScaleDbId for the BrAPIObservationVariableController GET endpoints + private String brapiScaleDbId; private Integer validValueMax; private Integer validValueMin; private Integer decimalPlaces; @@ -89,6 +93,7 @@ public void setBrAPIProperties(BrAPIScale brApiScale) { this.setValidValueMin(validValues.getMin()); this.setCategories(validValues.getCategories()); } + this.setBrapiScaleDbId(brApiScale.getScaleDbId()); } } diff --git a/src/main/java/org/breedinginsight/model/Trait.java b/src/main/java/org/breedinginsight/model/Trait.java index 0dae446bf..f476aac66 100644 --- a/src/main/java/org/breedinginsight/model/Trait.java +++ b/src/main/java/org/breedinginsight/model/Trait.java @@ -29,12 +29,10 @@ import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; import org.brapi.v2.model.pheno.BrAPIObservationVariable; -import org.breedinginsight.api.deserializer.ArrayOfStringDeserializer; import org.breedinginsight.api.deserializer.ListOfStringDeserializer; import org.breedinginsight.dao.db.tables.pojos.TraitEntity; import org.jooq.Record; -import javax.validation.Valid; import java.util.List; import static org.breedinginsight.dao.db.Tables.TRAIT; @@ -64,6 +62,9 @@ public class Trait extends TraitEntity { // Properties from brapi private String observationVariableDbId; + // brapiTraitDbId is only used to convert the bi trait id to brapiTraitDbId for the BrAPIObservationVariableController GET endpoints + @JsonIgnore + private String brapiTraitDbId; private String traitClass; private String traitDescription; private String attribute; @@ -122,6 +123,7 @@ public void setBrAPIProperties(BrAPIObservationVariable brApiVariable) { this.setEntity(brApiVariable.getTrait().getEntity()); this.setMainAbbreviation(brApiVariable.getTrait().getMainAbbreviation()); this.setSynonyms(brApiVariable.getTrait().getSynonyms()); + this.setBrapiTraitDbId(brApiVariable.getTrait().getTraitDbId()); } this.setObservationVariableDbId(brApiVariable.getObservationVariableDbId()); this.setDefaultValue(brApiVariable.getDefaultValue()); From 9490d56e881b5f7b495bd12b8628453c8463eb8a Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Tue, 3 Nov 2026 14:14:31 -0500 Subject: [PATCH 6/7] Unify response data between single variableDbId get and general get --- .../BrAPIObservationVariableController.java | 20 ++++++-- .../v2/dao/BrAPIObservationVariableDAO.java | 50 ------------------- .../BrAPIObservationVariableService.java | 40 +++++++++------ 3 files changed, 43 insertions(+), 67 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java index fb183b706..ad1bed214 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java @@ -150,15 +150,29 @@ public HttpResponse variablesGet(@PathVari public HttpResponse variablesObservationVariableDbIdGet(@PathVariable("programId") UUID programId, @PathVariable("observationVariableDbId") String observationVariableDbId) { log.debug("fetching variable: " + observationVariableDbId); + try { + // Check if submitted ID is valid UUID + UUID.fromString(observationVariableDbId); + } catch (IllegalArgumentException e) { + return HttpResponse.notFound(); + } try { - BrAPIObservationVariableSingleResponse response = observationVariableService.getBrAPIObservationVariableByDbId(observationVariableDbId, programId); + List programTraits = traitService.getByProgramId(programId, true); + + if(programTraits.isEmpty()) { + return HttpResponse.notFound(); + } + + BrAPIObservationVariable observationVariable = observationVariableService.filterVariableById(programTraits, observationVariableDbId); + + BrAPIObservationVariableSingleResponse response = new BrAPIObservationVariableSingleResponse() + .metadata(new BrAPIMetadata()) + .result(observationVariable); return HttpResponse.ok(response); } catch (DoesNotExistException e) { return HttpResponse.notFound(); - } catch (ApiException e) { - return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "error fetching variables"); } } diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java index 47d9ad165..87b2158ca 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java @@ -65,54 +65,4 @@ public List getVariableByName(List variableNam ); } - public BrAPIObservationVariableSingleResponse getVariableByIdWithResponse(String observationVariableDbId, Program program) throws DoesNotExistException { - ObservationVariablesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), ObservationVariablesApi.class); - - try { - ApiResponse apiResponse = api.variablesObservationVariableDbIdGet(observationVariableDbId); - - String brapiProgramDbId = brAPIDAOUtil.getBrAPIProgramDbId(program.getId()); - - if (apiResponse.getBody() != null && apiResponse.getBody().getResult() != null && apiResponse.getStatusCode() == 200) { - BrAPIObservationVariable returnedObsVar = apiResponse.getBody().getResult(); - - if (returnedObsVar.getProgramDbId() == null || !returnedObsVar.getProgramDbId().equals(brapiProgramDbId)) { - throw new DoesNotExistException(String.format("A observation variable with id [%s] was found but it does not belong to the submitted program with id: [%s]", observationVariableDbId, program.getId())); - } - - processObservationVariables(List.of(returnedObsVar), program); - - return apiResponse.getBody(); - } - } catch (ApiException e) { - if (e.getCode() >= 400 && e.getCode() < 500) { - throw new DoesNotExistException(String.format("Observation variable with brapi db id [%s] was not found for program [%s]", observationVariableDbId, program.getId())); - } - } - - throw new InternalServerException(String.format("Unable to retrieve BrAPI Observation Variable using id [%s] for BI Program with id: [%s]", observationVariableDbId, program.getId())); - } - - private void processObservationVariables(List observationVariables, Program program) { - - observationVariables.forEach(observationVariable -> { - - if (observationVariable.getObservationVariableName() != null) { - observationVariable.setObservationVariableName(Utilities.removeProgramKey(observationVariable.getObservationVariableName(), program.getKey())); - } - - if (observationVariable.getScale() != null && observationVariable.getScale().getScaleName() != null) { - observationVariable.getScale().setScaleName(Utilities.removeProgramKey(observationVariable.getScale().getScaleName(), program.getKey())); - } - - if (observationVariable.getMethod() != null && observationVariable.getMethod().getMethodName() != null) { - observationVariable.getMethod().setMethodName(Utilities.removeProgramKey(observationVariable.getMethod().getMethodName(), program.getKey())); - } - - if (observationVariable.getTrait() != null && observationVariable.getTrait().getTraitName() != null) { - observationVariable.getTrait().setTraitName(Utilities.removeProgramKey(observationVariable.getTrait().getTraitName(), program.getKey())); - } - }); - } - } diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java index 66c4adde9..e5c7c72dd 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java @@ -17,6 +17,7 @@ package org.breedinginsight.brapi.v2.services; +import io.micronaut.http.server.exceptions.InternalServerException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -91,6 +92,31 @@ public List getBrAPIObservationVariablesForExperiment( return new ArrayList<>(); } + @NotNull + public BrAPIObservationVariable filterVariableById(List programTraits, + String observationVariableDbId) throws DoesNotExistException { + List filterResult = filterVariables(programTraits, + Optional.of(observationVariableDbId), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty() + ); + + if (filterResult.isEmpty()) { + throw new DoesNotExistException(String.format("No observation variables found for brapi observation variable dbId [%s]", observationVariableDbId)); + } else if (filterResult.size() > 1) { + throw new InternalServerException(String.format("Multiple observation variables found for brapi observation variable db id [%s]. This should not be possible considering this id is unique.", observationVariableDbId)); + } + + return filterResult.get(0); + } + @NotNull public List filterVariables(List programTraits, Optional observationVariableDbId, @@ -230,18 +256,4 @@ private List prepSynonyms(Trait trait) { } return preppedSynonyms; } - - public BrAPIObservationVariableSingleResponse getBrAPIObservationVariableByDbId(String observationVariableDbId, UUID programId) throws ApiException, DoesNotExistException { - - if (!programService.exists(programId)) { - throw new DoesNotExistException("Program does not exist"); - } - - // This helper method retrieves the proper program that will be associated with the brapi observation variable that is being looked up. - UUID ontologyProgramLookupId = ontologyService.getSubscribedOntologyProgramId(programId); - - Program program = programService.getById(ontologyProgramLookupId).get(); - - return observationVariableDAO.getVariableByIdWithResponse(observationVariableDbId, program); - } } From ad4c030d5a17482e97bfbb5eae86223b7f7273c7 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Tue, 3 Nov 2026 14:23:33 -0500 Subject: [PATCH 7/7] Add clarifying comment --- .../brapi/v2/services/BrAPIObservationVariableService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java index e5c7c72dd..4968695a4 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java @@ -196,6 +196,8 @@ public List filterVariables(List programTraits, } public BrAPIObservationVariable convertToBrAPI(Trait trait) { + // This data is not stored in BrAPI by DeltaBreed, which is why it is being set with the DeltaBreed dbIds instead. + // They can be filtered upon in BrAPIObservationVariableController.variablesGet() BrAPIOntologyReference brAPIOntologyReference = new BrAPIOntologyReference().ontologyDbId(trait.getProgramOntologyId() .toString()); String status = trait.getActive() ? "active" : "inactive";