diff --git a/src/main/java/org/breedinginsight/brapi/v2/ExperimentController.java b/src/main/java/org/breedinginsight/api/v1/controller/ExperimentController.java similarity index 63% rename from src/main/java/org/breedinginsight/brapi/v2/ExperimentController.java rename to src/main/java/org/breedinginsight/api/v1/controller/ExperimentController.java index c819cc10c..017197219 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/ExperimentController.java +++ b/src/main/java/org/breedinginsight/api/v1/controller/ExperimentController.java @@ -1,4 +1,4 @@ -package org.breedinginsight.brapi.v2; +package org.breedinginsight.api.v1.controller; import io.micronaut.http.HttpHeaders; import io.micronaut.http.HttpResponse; @@ -9,28 +9,22 @@ import io.micronaut.security.annotation.Secured; import io.micronaut.security.rules.SecurityRule; import lombok.extern.slf4j.Slf4j; -import org.brapi.client.v2.model.exceptions.ApiException; -import org.brapi.v2.model.core.BrAPITrial; import org.breedinginsight.api.auth.ProgramSecured; import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; -import org.breedinginsight.api.model.v1.request.query.SearchRequest; -import org.breedinginsight.api.model.v1.response.DataResponse; import org.breedinginsight.api.model.v1.response.Response; -import org.breedinginsight.api.model.v1.validators.QueryValid; -import org.breedinginsight.brapi.v1.controller.BrapiVersion; import org.breedinginsight.brapi.v2.model.request.query.ExperimentExportQuery; -import org.breedinginsight.brapi.v2.model.request.query.ExperimentQuery; import org.breedinginsight.brapi.v2.services.BrAPITrialService; import org.breedinginsight.model.Dataset; import org.breedinginsight.model.DownloadFile; import org.breedinginsight.model.Program; import org.breedinginsight.services.ProgramService; import org.breedinginsight.services.exceptions.DoesNotExistException; -import org.breedinginsight.utilities.response.ResponseUtils; import org.breedinginsight.utilities.response.mappers.ExperimentQueryMapper; + import javax.inject.Inject; import javax.validation.Valid; -import java.util.*; +import java.util.Optional; +import java.util.UUID; @Slf4j @Controller @@ -47,48 +41,6 @@ public ExperimentController(BrAPITrialService experimentService, ExperimentQuery this.programService = programService; } - @Get("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/trials{?queryParams*}") - @Produces(MediaType.APPLICATION_JSON) - @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) - public HttpResponse>>> getExperiments( - @PathVariable("programId") UUID programId, - @QueryValue @QueryValid(using = ExperimentQueryMapper.class) @Valid ExperimentQuery queryParams) { - try { - log.debug("fetching trials for program: " + programId); - - List experiments = experimentService.getExperiments(programId); - SearchRequest searchRequest = queryParams.constructSearchRequest(); - return ResponseUtils.getBrapiQueryResponse(experiments, experimentQueryMapper, queryParams, searchRequest); - } catch (ApiException e) { - log.info(e.getMessage(), e); - return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error retrieving experiments"); - } catch (DoesNotExistException e) { - log.info(e.getMessage(), e); - return HttpResponse.status(HttpStatus.NOT_FOUND, e.getMessage()); - } - } - - @Get("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/trials/{trialId}") - @Produces(MediaType.APPLICATION_JSON) - @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) - public HttpResponse> getExperimentById( - @PathVariable("programId") UUID programId, - @PathVariable("trialId") UUID trialId, - @QueryValue(defaultValue = "false") Boolean stats){ - try { - String logMsg = "fetching trial id:" + trialId +" for program: " + programId; - if(stats){ - logMsg += " with stats"; - } - log.debug(logMsg); - Response response = new Response<>(experimentService.getTrialDataByUUID(programId, trialId, stats)); - return HttpResponse.ok(response); - } catch (DoesNotExistException e) { - log.info(e.getMessage(), e); - return HttpResponse.status(HttpStatus.NOT_FOUND, e.getMessage()); - } - } - @Get("/${micronaut.bi.api.version}/programs/{programId}/experiments/{experimentId}/export{?queryParams*}") @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) @Produces(value={"text/csv", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/octet-stream"}) diff --git a/src/main/java/org/breedinginsight/api/v1/controller/TokenController.java b/src/main/java/org/breedinginsight/api/v1/controller/TokenController.java index 9cae400fa..6c396cb9d 100644 --- a/src/main/java/org/breedinginsight/api/v1/controller/TokenController.java +++ b/src/main/java/org/breedinginsight/api/v1/controller/TokenController.java @@ -52,7 +52,7 @@ public TokenController(SecurityService securityService, TokenService tokenServic @Get("/api-token{?returnUrl}") @Secured(SecurityRule.IS_AUTHENTICATED) - public HttpResponse apiToken(@QueryValue @Nullable String returnUrl) { + public HttpResponse apiToken(@QueryValue @Nullable String returnUrl) { AuthenticatedUser actingUser = securityService.getUser(); Optional token = tokenService.generateApiToken(actingUser); @@ -61,12 +61,12 @@ public HttpResponse apiToken(@QueryValue @Nullable String returnUrl) { ApiToken apiToken = token.get(); if(returnUrl != null) { - if(StringUtils.trim(returnUrl).isEmpty()) { + if(StringUtils.trim(returnUrl).isEmpty() || "undefined".equalsIgnoreCase(returnUrl)) { return HttpResponse.badRequest("returnUrl cannot be blank"); } URI location = UriBuilder.of(returnUrl) .queryParam("status", 200) - .queryParam("token", apiToken.getAccessToken()) + .queryParam("access_token", apiToken.getAccessToken()) .build(); return HttpResponse.seeOther(location) diff --git a/src/main/java/org/breedinginsight/api/v1/controller/auth/OIDCDiscoveryController.java b/src/main/java/org/breedinginsight/api/v1/controller/auth/OIDCDiscoveryController.java new file mode 100644 index 000000000..8560cb445 --- /dev/null +++ b/src/main/java/org/breedinginsight/api/v1/controller/auth/OIDCDiscoveryController.java @@ -0,0 +1,69 @@ +/* + * 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.api.v1.controller.auth; + +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Produces; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import org.breedinginsight.dao.db.tables.pojos.ProgramEntity; +import org.breedinginsight.daos.ProgramDAO; + +import javax.inject.Inject; +import java.util.UUID; + +@Controller +@Secured(SecurityRule.IS_ANONYMOUS) +public class OIDCDiscoveryController { + + private static final String OIDC_CONFIG = " {\n" + + " \"issuer\": \"%s\",\n" + + " \"authorization_endpoint\": \"%s/programs/%s/brapi/authorize\",\n" + + " \"jwks_uri\": \"\",\n" + + " \"token_endpoint\": \"\",\n" + + " \"grant_types_supported\": [\"implicit\"],\n" + + " \"response_types_supported\": [\"token\"],\n" + + " \"subject_types_supported\": [\"public\"],\n" + + " \"id_token_signing_alg_values_supported\": []\n" + + " }\n"; + + private final String webUrl; + private final ProgramDAO programDAO; + + @Inject + public OIDCDiscoveryController(@Property(name="web.base-url") String webUrl, ProgramDAO programDAO) { + this.webUrl = webUrl; + this.programDAO = programDAO; + } + + + @Get("/${micronaut.bi.api.version}/programs/{programId}/.well-known/openid-configuration") + @Produces(MediaType.APPLICATION_JSON) + public HttpResponse getOpenIdConfig(UUID programId) { + ProgramEntity programEntity = programDAO.fetchOneById(programId); + if(programEntity != null) { + return HttpResponse.ok(String.format(OIDC_CONFIG, webUrl, webUrl, programId)); + } + + return HttpResponse.notFound("unknown program"); + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/GermplasmController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIGermplasmController.java similarity index 98% rename from src/main/java/org/breedinginsight/brapi/v2/GermplasmController.java rename to src/main/java/org/breedinginsight/brapi/v2/BrAPIGermplasmController.java index 568e58d99..edc89b5f0 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/GermplasmController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIGermplasmController.java @@ -51,7 +51,7 @@ @Slf4j @Controller("/${micronaut.bi.api.version}") @Secured(SecurityRule.IS_AUTHENTICATED) -public class GermplasmController { +public class BrAPIGermplasmController { private final BrAPIGermplasmService germplasmService; private final GermplasmQueryMapper germplasmQueryMapper; @@ -64,7 +64,7 @@ public class GermplasmController { @Inject - public GermplasmController(BrAPIGermplasmService germplasmService, GermplasmQueryMapper germplasmQueryMapper, ProgramDAO programDAO, BrAPIGermplasmDAO germplasmDAO, GenotypeService genoService, BrAPIEndpointProvider brAPIEndpointProvider) { + public BrAPIGermplasmController(BrAPIGermplasmService germplasmService, GermplasmQueryMapper germplasmQueryMapper, ProgramDAO programDAO, BrAPIGermplasmDAO germplasmDAO, GenotypeService genoService, BrAPIEndpointProvider brAPIEndpointProvider) { this.germplasmService = germplasmService; this.germplasmQueryMapper = germplasmQueryMapper; this.programDAO = programDAO; diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIImagesController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIImagesController.java new file mode 100644 index 000000000..f760af9f0 --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIImagesController.java @@ -0,0 +1,90 @@ +/* + * 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; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.*; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import lombok.extern.slf4j.Slf4j; +import org.brapi.v2.model.pheno.BrAPIImage; +import org.breedinginsight.api.auth.ProgramSecured; +import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; + +import java.util.List; +import java.util.UUID; + +@Slf4j +@Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class BrAPIImagesController { + /* + TODO + - POST images + - PUT imagesImageDbIdImagecontent + - PUT imagesImageDbIdPut + */ + @Get("/images") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse imagesGet(@PathVariable("programId") UUID programId, + @QueryValue("imageDbId") String imageDbId, + @QueryValue("imageName") String imageName, + @QueryValue("observationUnitDbId") String observationUnitDbId, + @QueryValue("observationDbId") String observationDbId, + @QueryValue("descriptiveOntologyTerm") String descriptiveOntologyTerm, + @QueryValue("commonCropName") String commonCropName, + @QueryValue("programDbId") String programDbId, + @QueryValue("externalReferenceID") String externalReferenceID, + @QueryValue("externalReferenceId") String externalReferenceId, + @QueryValue("externalReferenceSource") String externalReferenceSource, + @QueryValue("page") Integer page, + @QueryValue("pageSize") Integer pageSize) { + return HttpResponse.notFound(); + } + + @Get("/images/{imageDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse imagesImageDbIdGet(@PathVariable("programId") UUID programId, + @PathVariable("imageDbId") String imageDbId) { + return HttpResponse.notFound(); + } + + @Put("/images/{imageDbId}/imagecontent") + @Consumes({"image/_*"}) + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse imagesImageDbIdImagecontentPut(@PathVariable("programId") UUID programId, + @PathVariable("imageDbId") String imageDbId, + @Body Object body) { + return HttpResponse.notFound(); + } + + @Put("/images/{imageDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse imagesImageDbIdPut(@PathVariable("programId") UUID programId, + @PathVariable("imageDbId") String imageDbId, + @Body BrAPIImage body) { + return HttpResponse.notFound(); + } + + @Post("/images") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse imagesPost(@PathVariable("programId") UUID programId, @Body List body) { + return HttpResponse.notFound(); + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/ListController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIListController.java similarity index 96% rename from src/main/java/org/breedinginsight/brapi/v2/ListController.java rename to src/main/java/org/breedinginsight/brapi/v2/BrAPIListController.java index 7b33629ef..115448d58 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/ListController.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIListController.java @@ -50,15 +50,15 @@ @Slf4j @Controller @Secured(SecurityRule.IS_AUTHENTICATED) -public class ListController { +public class BrAPIListController { private final ProgramService programService; private final BrAPIListService listService; private final ListQueryMapper listQueryMapper; @Inject - public ListController(ProgramService programService, BrAPIListService listService, - ListQueryMapper listQueryMapper) { + public BrAPIListController(ProgramService programService, BrAPIListService listService, + ListQueryMapper listQueryMapper) { this.programService = programService; this.listService = listService; this.listQueryMapper = listQueryMapper; diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationLevelsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationLevelsController.java new file mode 100644 index 000000000..9c54bed9a --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationLevelsController.java @@ -0,0 +1,142 @@ +/* + * 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; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; +import io.micronaut.http.annotation.QueryValue; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import lombok.extern.slf4j.Slf4j; +import org.brapi.client.v2.ApiResponse; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.client.v2.modules.phenotype.ObservationUnitsApi; +import org.brapi.v2.model.BrAPIIndexPagination; +import org.brapi.v2.model.BrAPIMetadata; +import org.brapi.v2.model.core.BrAPIStudy; +import org.brapi.v2.model.core.BrAPITrial; +import org.brapi.v2.model.pheno.BrAPIObservationUnitHierarchyLevel; +import org.brapi.v2.model.pheno.response.BrAPIObservationLevelListResponse; +import org.brapi.v2.model.pheno.response.BrAPIObservationLevelListResponseResult; +import org.breedinginsight.api.auth.ProgramSecured; +import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; +import org.breedinginsight.brapi.v2.dao.BrAPIStudyDAO; +import org.breedinginsight.brapi.v2.dao.BrAPITrialDAO; +import org.breedinginsight.daos.ProgramDAO; +import org.breedinginsight.model.BrAPIConstants; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.services.brapi.BrAPIEndpointProvider; +import org.breedinginsight.utilities.Utilities; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +@Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class BrAPIObservationLevelsController { + + private final BrAPIEndpointProvider brAPIEndpointProvider; + private final ProgramDAO programDAO; + private final ProgramService programService; + private final BrAPITrialDAO trialDAO; + private final BrAPIStudyDAO studyDAO; + + @Inject + public BrAPIObservationLevelsController(BrAPIEndpointProvider brAPIEndpointProvider, ProgramDAO programDAO, ProgramService programService, BrAPITrialDAO trialDAO, BrAPIStudyDAO studyDAO) { + this.brAPIEndpointProvider = brAPIEndpointProvider; + this.programDAO = programDAO; + this.programService = programService; + this.trialDAO = trialDAO; + this.studyDAO = studyDAO; + } + + @Get("/observationlevels") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationlevelsGet(@PathVariable("programId") UUID programId, + @Nullable @QueryValue("trialDbId") String experimentId, + @Nullable @QueryValue("studyDbId") String environmentId, + @Nullable @QueryValue("page") Integer page, + @Nullable @QueryValue("pageSize") Integer pageSize) { + + log.debug("fetching observation levels for programId: " + programId); + + Optional program = programService.getById(programId); + if(program.isEmpty()) { + log.warn("Program id: " + programId + " not found"); + return HttpResponse.notFound(); + } + + String programDbId = program.get().getBrapiProgram().getProgramDbId(); + String studyDbId = null; + String trialDbId = null; + + if(environmentId != null) { + try { + Optional study = studyDAO.getStudyByEnvironmentId(UUID.fromString(environmentId), program.get()); + if(study.isPresent()) { + studyDbId = study.get().getStudyDbId(); + } else { + studyDbId = environmentId; + } + } catch (ApiException e) { + log.error(Utilities.generateApiExceptionLogMessage(e), "Error fetching environment"); + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error finding observation levels"); + } + } else if(experimentId != null) { + try { + List trial = trialDAO.getTrialsByExperimentIds(List.of(UUID.fromString(experimentId)), program.get()); + if(trial.size() == 1) { + trialDbId = trial.get(0).getTrialDbId(); + } else { + trialDbId = experimentId; + } + } catch (ApiException e) { + log.error(Utilities.generateApiExceptionLogMessage(e), "Error fetching experiments"); + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error finding observation levels"); + } + } + + ObservationUnitsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationUnitsApi.class); + try { + ApiResponse response = api.observationlevelsGet(studyDbId, trialDbId, programDbId, page == null ? 0 : page, pageSize == null ? 1000 : pageSize); + BrAPIIndexPagination responsePagination = (BrAPIIndexPagination) response.getBody().getMetadata().getPagination(); + List levels = new ArrayList<>(); + if(response.getBody() != null) { + levels = response.getBody().getResult().getData().stream().filter(level -> !level.getLevelName().equals(BrAPIConstants.REPLICATE.getValue()) && !level.getLevelName().equals(BrAPIConstants.BLOCK.getValue())).collect( + Collectors.toList()); + } + log.debug(String.format("found %d observation levels", levels.size())); + return HttpResponse.ok(new BrAPIObservationLevelListResponse().metadata(new BrAPIMetadata().pagination(responsePagination)) + .result(new BrAPIObservationLevelListResponseResult().data(levels))); + } catch (ApiException e) { + log.error(Utilities.generateApiExceptionLogMessage(e), "Error fetching observation levels"); + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error finding observation levels"); + } + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationUnitController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationUnitController.java new file mode 100644 index 000000000..c649626b1 --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationUnitController.java @@ -0,0 +1,243 @@ +/* + * 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; + +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.*; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import lombok.extern.slf4j.Slf4j; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.v2.model.BrAPIIndexPagination; +import org.brapi.v2.model.BrAPIMetadata; +import org.brapi.v2.model.BrAPIStatus; +import org.brapi.v2.model.pheno.BrAPIObservationUnit; +import org.brapi.v2.model.pheno.response.BrAPIObservationUnitListResponse; +import org.brapi.v2.model.pheno.response.BrAPIObservationUnitListResponseResult; +import org.brapi.v2.model.pheno.response.BrAPIObservationUnitSingleResponse; +import org.breedinginsight.api.auth.ProgramSecured; +import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; +import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationUnitDAO; +import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.utilities.Utilities; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +@Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class BrAPIObservationUnitController { + private final String referenceSource; + + private final BrAPIObservationUnitDAO observationUnitDAO; + + private final ProgramService programService; + + @Inject + public BrAPIObservationUnitController(@Property(name = "brapi.server.reference-source") String referenceSource, BrAPIObservationUnitDAO observationUnitDAO, ProgramService programService) { + this.referenceSource = referenceSource; + this.observationUnitDAO = observationUnitDAO; + this.programService = programService; + } + + @Get("/observationunits") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationunitsGet(@PathVariable("programId") UUID programId, + @Nullable @QueryValue("observationUnitDbId") String observationUnitDbId, + @Nullable @QueryValue("observationUnitName") String observationUnitName, + @Nullable @QueryValue("locationDbId") String locationDbId, + @Nullable @QueryValue("seasonDbId") String seasonDbId, + @Nullable @QueryValue("includeObservations") Boolean includeObservations, + @Nullable @QueryValue("observationUnitLevelName") String observationUnitLevelName, + @Nullable @QueryValue("observationUnitLevelOrder") String observationUnitLevelOrder, + @Nullable @QueryValue("observationUnitLevelCode") String observationUnitLevelCode, + @Nullable @QueryValue("observationUnitLevelRelationshipName") String observationUnitLevelRelationshipName, + @Nullable @QueryValue("observationUnitLevelRelationshipOrder") String observationUnitLevelRelationshipOrder, + @Nullable @QueryValue("observationUnitLevelRelationshipCode") String observationUnitLevelRelationshipCode, + @Nullable @QueryValue("observationUnitLevelRelationshipDbId") String observationUnitLevelRelationshipDbId, + @Nullable @QueryValue("commonCropName") String commonCropName, + @Nullable @QueryValue("trialDbId") String trialDbId, + @Nullable @QueryValue("studyDbId") String studyDbId, + @Nullable @QueryValue("germplasmDbId") String germplasmDbId, + @Nullable @QueryValue("externalReferenceID") String externalReferenceID, + @Nullable @QueryValue("externalReferenceId") String externalReferenceId, + @Nullable @QueryValue("externalReferenceSource") String externalReferenceSource, + @Nullable @QueryValue("page") Integer page, + @Nullable @QueryValue("pageSize") Integer pageSize) { + + log.debug("observationunitsGet: fetching ous by filters"); + + Optional program = programService.getById(programId); + if(program.isEmpty()) { + log.warn("Program id: " + programId + " not found"); + return HttpResponse.notFound(); + } + + try { + Optional levelOrder = Optional.empty(); + Optional levelRelationshipOrder = Optional.empty(); + if(observationUnitLevelOrder != null) { + levelOrder = Optional.of(Integer.parseInt(observationUnitLevelOrder)); + } + if(observationUnitLevelRelationshipOrder != null) { + levelOrder = Optional.of(Integer.parseInt(observationUnitLevelRelationshipOrder)); + } + + List observationUnits = observationUnitDAO.getObservationUnits(program.get(), + Optional.ofNullable(observationUnitDbId), + Optional.ofNullable(observationUnitName), + Optional.ofNullable(locationDbId), + Optional.ofNullable(seasonDbId), + Optional.ofNullable(includeObservations), + Optional.ofNullable(observationUnitLevelName), + levelOrder, + Optional.ofNullable(observationUnitLevelCode), + Optional.ofNullable(observationUnitLevelRelationshipName), + levelRelationshipOrder, + Optional.ofNullable(observationUnitLevelRelationshipCode), + Optional.ofNullable(observationUnitLevelRelationshipDbId), + Optional.ofNullable(commonCropName), + Optional.ofNullable(trialDbId), + Optional.ofNullable(studyDbId), + Optional.ofNullable(germplasmDbId)) + .stream() + .peek(this::setDbIds) + .collect(Collectors.toList()); + + return HttpResponse.ok(new BrAPIObservationUnitListResponse().metadata(new BrAPIMetadata().pagination(new BrAPIIndexPagination().currentPage(0) + .totalPages(1) + .pageSize(observationUnits.size()) + .totalCount(observationUnits.size()))) + .result(new BrAPIObservationUnitListResponseResult().data(observationUnits))); + } catch (ApiException e) { + log.error(Utilities.generateApiExceptionLogMessage(e), e); + return HttpResponse.serverError(new BrAPIObservationUnitListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) + .message("Error fetching observation units"))))); + } catch (Exception e) { + log.error("Error fetching OUs", e); + return HttpResponse.serverError(new BrAPIObservationUnitListResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) + .message("Error fetching observation units"))))); + } + } + + @Get("/observationunits/{observationUnitDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationunitsObservationUnitDbIdGet(@PathVariable("programId") UUID programId, @PathVariable("observationUnitDbId") String observationUnitDbId) { + log.debug("observationunitsObservationUnitDbIdGet: fetching ou by externalReferenceId: " + observationUnitDbId); + Optional program = programService.getById(programId); + if(program.isEmpty()) { + log.warn("Program id: " + programId + " not found"); + return HttpResponse.notFound(); + } + try { + List ous = observationUnitDAO.getObservationUnitsById(List.of(observationUnitDbId), program.get()); + if(ous.size() != 1) { + log.warn("did not find a single ou with externalReferenceId: " + observationUnitDbId); + return HttpResponse.notFound(); + } + setDbIds(ous.get(0)); + return HttpResponse.ok(new BrAPIObservationUnitSingleResponse().result(ous.get(0))); + } catch (ApiException e) { + log.error(Utilities.generateApiExceptionLogMessage(e), e); + return HttpResponse.serverError(new BrAPIObservationUnitSingleResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) + .message("Error fetching observation unit"))))); + } catch (Exception e) { + log.error("Error fetching OU", e); + return HttpResponse.serverError(new BrAPIObservationUnitSingleResponse().metadata(new BrAPIMetadata().status(List.of(new BrAPIStatus().messageType(BrAPIStatus.MessageTypeEnum.ERROR) + .message("Error fetching observation unit"))))); + } + } + + @Put("/observationunits/{observationUnitDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationunitsObservationUnitDbIdPut(@PathVariable("programId") UUID programId, @PathVariable("observationUnitDbId") String observationUnitDbId, @Body BrAPIObservationUnit body) { + //DO NOT IMPLEMENT - Users aren't yet able to update observation units + return HttpResponse.notFound(); + } + + @Post("/observationunits") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationunitsPost(@PathVariable("programId") UUID programId, @Body List body) { + //DO NOT IMPLEMENT - Users are only able to create observation units via the DeltaBreed UI + return HttpResponse.notFound(); + } + + @Put("/observationunits") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationunitsPut(@PathVariable("programId") UUID programId, @Body Map body) { + //DO NOT IMPLEMENT - Users aren't yet able to update observation units + return HttpResponse.notFound(); + } + + @Get("/observationunits/table") + @Produces({"application/json", "text/csv", "text/tsv"}) + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationunitsTableGet(@PathVariable("programId") UUID programId, + @Nullable @Header("Accept") String accept, + @Nullable @QueryValue("observationUnitDbId") String observationUnitDbId, + @Nullable @QueryValue("observationVariableDbId") String observationVariableDbId, + @Nullable @QueryValue("locationDbId") String locationDbId, + @Nullable @QueryValue("seasonDbId") String seasonDbId, + @Nullable @QueryValue("observationLevel") String observationLevel, + @Nullable @QueryValue("programDbId") String programDbId, + @Nullable @QueryValue("trialDbId") String trialDbId, + @Nullable @QueryValue("studyDbId") String studyDbId, + @Nullable @QueryValue("germplasmDbId") String germplasmDbId, + @Nullable @QueryValue("observationUnitLevelName") String observationUnitLevelName, + @Nullable @QueryValue("observationUnitLevelOrder") String observationUnitLevelOrder, + @Nullable @QueryValue("observationUnitLevelCode") String observationUnitLevelCode, + @Nullable @QueryValue("observationUnitLevelRelationshipName") String observationUnitLevelRelationshipName, + @Nullable @QueryValue("observationUnitLevelRelationshipOrder") String observationUnitLevelRelationshipOrder, + @Nullable @QueryValue("observationUnitLevelRelationshipCode") String observationUnitLevelRelationshipCode, + @Nullable @QueryValue("observationUnitLevelRelationshipDbId") String observationUnitLevelRelationshipDbId) { + return HttpResponse.notFound(); + } + + private void setDbIds(BrAPIObservationUnit ou) { + ou.observationUnitDbId(Utilities.getExternalReference(ou.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.OBSERVATION_UNITS)) + .orElseThrow(() -> new IllegalStateException("No BI external reference found")) + .getReferenceID()); + ou.studyDbId(Utilities.getExternalReference(ou.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES)) + .orElseThrow(() -> new IllegalStateException("No BI external reference found")) + .getReferenceID()); + ou.trialDbId(Utilities.getExternalReference(ou.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.TRIALS)) + .orElseThrow(() -> new IllegalStateException("No BI external reference found")) + .getReferenceID()); + ou.programDbId(Utilities.getExternalReference(ou.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.PROGRAMS)) + .orElseThrow(() -> new IllegalStateException("No BI external reference found")) + .getReferenceID()); + if (ou.getAdditionalInfo().has(BrAPIAdditionalInfoFields.GERMPLASM_UUID)) { + ou.setGermplasmDbId(ou.getAdditionalInfo() + .get(BrAPIAdditionalInfoFields.GERMPLASM_UUID) + .getAsString()); + } + + //TODO update locationDbId + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java new file mode 100644 index 000000000..45d6983f3 --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationVariableController.java @@ -0,0 +1,193 @@ +/* + * 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; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.*; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import lombok.extern.slf4j.Slf4j; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.v2.model.BrAPIIndexPagination; +import org.brapi.v2.model.BrAPIMetadata; +import org.brapi.v2.model.pheno.BrAPIObservationVariable; +import org.brapi.v2.model.pheno.response.BrAPIObservationVariableListResponse; +import org.brapi.v2.model.pheno.response.BrAPIObservationVariableListResponseResult; +import org.brapi.v2.model.pheno.response.BrAPIObservationVariableSingleResponse; +import org.breedinginsight.api.auth.ProgramSecured; +import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; +import org.breedinginsight.brapi.v2.services.BrAPIObservationVariableService; +import org.breedinginsight.brapi.v2.services.BrAPITrialService; +import org.breedinginsight.model.Trait; +import org.breedinginsight.services.OntologyService; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.services.TraitService; +import org.breedinginsight.services.exceptions.DoesNotExistException; +import org.breedinginsight.utilities.Utilities; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Slf4j +@Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class BrAPIObservationVariableController { + + private final OntologyService ontologyService; + private final BrAPIObservationVariableService observationVariableService; + private final TraitService traitService; + private final BrAPITrialService trialService; + private final ProgramService programService; + + @Inject + public BrAPIObservationVariableController(OntologyService ontologyService, + BrAPIObservationVariableService observationVariableService, + TraitService traitService, + BrAPITrialService trialService, + ProgramService programService) { + this.ontologyService = ontologyService; + this.observationVariableService = observationVariableService; + this.traitService = traitService; + this.trialService = trialService; + this.programService = programService; + } + + @Get("/variables") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse variablesGet(@PathVariable("programId") UUID programId, + @Nullable @QueryValue("observationVariableDbId") String observationVariableDbId, + @Nullable @QueryValue("observationVariableName") String observationVariableName, + @Nullable @QueryValue("observationVariablePUI") String observationVariablePUI, + @Nullable @QueryValue("traitClass") String traitClass, + @Nullable @QueryValue("methodDbId") String methodDbId, + @Nullable @QueryValue("methodName") String methodName, + @Nullable @QueryValue("methodPUI") String methodPUI, + @Nullable @QueryValue("scaleDbId") String scaleDbId, + @Nullable @QueryValue("scaleName") String scaleName, + @Nullable @QueryValue("scalePUI") String scalePUI, + @Nullable @QueryValue("traitDbId") String traitDbId, + @Nullable @QueryValue("traitName") String traitName, + @Nullable @QueryValue("traitPUI") String traitPUI, + @Nullable @QueryValue("ontologyDbId") String ontologyDbId, + @Nullable @QueryValue("commonCropName") String commonCropName, + @Nullable @QueryValue("trialDbId") String experimentId, + @Nullable @QueryValue("studyDbId") String environmentId, + @Nullable @QueryValue("externalReferenceID") String externalReferenceID, + @Nullable @QueryValue("externalReferenceId") String externalReferenceId, + @Nullable @QueryValue("externalReferenceSource") String externalReferenceSource, + @Nullable @QueryValue("page") Integer page, + @Nullable @QueryValue("pageSize") Integer pageSize, HttpRequest request) { + + try { + List programTraits; + if (observationVariablePUI != null || methodPUI != null || scalePUI != null || traitPUI != null || commonCropName != null || externalReferenceID != null || externalReferenceId != null || externalReferenceSource != null) { + log.debug("unsupported variable filters, returning"); + programTraits = new ArrayList<>(); + } else if(environmentId != null || experimentId != null) { + programTraits = observationVariableService.getBrAPIObservationVariablesForExperiment( + programId, Optional.ofNullable(experimentId), Optional.ofNullable(environmentId)); + } else { + log.debug("fetching variables for the program: " + programId); + programTraits = ontologyService.getTraitsByProgramId(programId, true); + + } + + List filteredObsVars = observationVariableService.filterVariables(programTraits, + Optional.ofNullable(observationVariableDbId), + Optional.ofNullable(observationVariableName), + Optional.ofNullable(traitClass), + Optional.ofNullable(methodDbId), + Optional.ofNullable(methodName), + Optional.ofNullable(scaleDbId), + Optional.ofNullable(scaleName), + Optional.ofNullable(traitDbId), + Optional.ofNullable(traitName), + Optional.ofNullable(ontologyDbId)); + + BrAPIObservationVariableListResponse response = new BrAPIObservationVariableListResponse() + .metadata(new BrAPIMetadata() + .pagination(new BrAPIIndexPagination() + .currentPage(0) + .totalPages(1) + .pageSize(filteredObsVars.size()) + .totalCount(filteredObsVars.size()))) + .result(new BrAPIObservationVariableListResponseResult().data(filteredObsVars)); + + return HttpResponse.ok(response); + } catch (DoesNotExistException e) { + log.warn("Couldn't find object", e); + return HttpResponse.notFound(); + } catch (ApiException e) { + log.error(Utilities.generateApiExceptionLogMessage(e), e); + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "error fetching variables"); + } + } + + @Get("/variables/{observationVariableDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + 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())); + + return HttpResponse.ok(response); + } catch (DoesNotExistException e) { + return HttpResponse.notFound(); + } + } + + @Put("/variables/{observationVariableDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse variablesObservationVariableDbIdPut(@PathVariable("programId") UUID programId, + @PathVariable("observationVariableDbId") String observationVariableDbId, + @Body BrAPIObservationVariable body) { + //DO NOT IMPLEMENT - Users are only able to update traits via the DeltaBreed UI + return HttpResponse.notFound(); + } + + @Post("/variables") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse variablesPost(@PathVariable("programId") UUID programId, @Body List body) { + //DO NOT IMPLEMENT - Users are only able to create new traits via the DeltaBreed UI + return HttpResponse.notFound(); + } + +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java new file mode 100644 index 000000000..5a6c51148 --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIObservationsController.java @@ -0,0 +1,138 @@ +/* + * 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; + + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.*; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import lombok.extern.slf4j.Slf4j; +import org.brapi.v2.model.pheno.BrAPIObservation; +import org.breedinginsight.api.auth.ProgramSecured; +import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; + +import javax.annotation.Nullable; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Slf4j +@Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class BrAPIObservationsController { + + @Get("/observations") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationsGet(@PathVariable("programId") UUID programId, + @Nullable @QueryValue("observationDbId") String observationDbId, + @Nullable @QueryValue("observationUnitDbId") String observationUnitDbId, + @Nullable @QueryValue("observationVariableDbId") String observationVariableDbId, + @Nullable @QueryValue("locationDbId") String locationDbId, + @Nullable @QueryValue("seasonDbId") String seasonDbId, + @Nullable @QueryValue("observationTimeStampRangeStart") Date observationTimeStampRangeStart, + @Nullable @QueryValue("observationTimeStampRangeEnd") Date observationTimeStampRangeEnd, + @Nullable @QueryValue("observationUnitLevelName") String observationUnitLevelName, + @Nullable @QueryValue("observationUnitLevelOrder") String observationUnitLevelOrder, + @Nullable @QueryValue("observationUnitLevelCode") String observationUnitLevelCode, + @Nullable @QueryValue("observationUnitLevelRelationshipName") String observationUnitLevelRelationshipName, + @Nullable @QueryValue("observationUnitLevelRelationshipOrder") String observationUnitLevelRelationshipOrder, + @Nullable @QueryValue("observationUnitLevelRelationshipCode") String observationUnitLevelRelationshipCode, + @Nullable @QueryValue("observationUnitLevelRelationshipDbId") String observationUnitLevelRelationshipDbId, + @Nullable @QueryValue("commonCropName") String commonCropName, + @Nullable @QueryValue("programDbId") String programDbId, + @Nullable @QueryValue("trialDbId") String trialDbId, + @Nullable @QueryValue("studyDbId") String studyDbId, + @Nullable @QueryValue("germplasmDbId") String germplasmDbId, + @Nullable @QueryValue("externalReferenceID") String externalReferenceID, + @Nullable @QueryValue("externalReferenceId") String externalReferenceId, + @Nullable @QueryValue("externalReferenceSource") String externalReferenceSource, + @Nullable @QueryValue("page") Integer page, + @Nullable @QueryValue("pageSize") Integer pageSize) { + //TODO + return HttpResponse.notFound(); + } + + @Get("/observations/{observationDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationsObservationDbIdGet(@PathVariable("programId") UUID programId, + @PathVariable("observationDbId") String observationDbId) { + return HttpResponse.notFound(); + } + + @Put("/observations/{observationDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationsObservationDbIdPut(@PathVariable("programId") UUID programId, + @PathVariable("observationDbId") String observationDbId, + @Body BrAPIObservation body) { + /* + DO NOT IMPLEMENT - users must create observations via file upload + TODO identify how observations uploaded via BrAPI will be separated from curated observations + */ + return HttpResponse.notFound(); + } + + @Post("/observations") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationsPost(@PathVariable("programId") UUID programId, @Body List body) { + /* + DO NOT IMPLEMENT - users must create observations via file upload + TODO identify how observations uploaded via BrAPI will be separated from curated observations + */ + return HttpResponse.notFound(); + } + + @Put("/observations") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationsPut(@PathVariable("programId") UUID programId, @Body Map body) { + /* + DO NOT IMPLEMENT - users must create observations via file upload + TODO identify how observations uploaded via BrAPI will be separated from curated observations + */ + return HttpResponse.notFound(); + } + + @Get("/observations/table") + @Produces({"application/json", "text/csv", "text/tsv"}) + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse observationsTableGet(@PathVariable("programId") UUID programId, + @Nullable @Header("Accept") String accept, + @Nullable @QueryValue("observationUnitDbId") String observationUnitDbId, + @Nullable @QueryValue("observationVariableDbId") String observationVariableDbId, + @Nullable @QueryValue("locationDbId") String locationDbId, + @Nullable @QueryValue("seasonDbId") String seasonDbId, + @Nullable @QueryValue("observationLevel") String observationLevel, + @Nullable @QueryValue("searchResultsDbId") String searchResultsDbId, + @Nullable @QueryValue("observationTimeStampRangeStart") Date observationTimeStampRangeStart, + @Nullable @QueryValue("observationTimeStampRangeEnd") Date observationTimeStampRangeEnd, + @Nullable @QueryValue("programDbId") String programDbId, + @Nullable @QueryValue("trialDbId") String trialDbId, + @Nullable @QueryValue("studyDbId") String studyDbId, + @Nullable @QueryValue("germplasmDbId") String germplasmDbId, + @Nullable @QueryValue("observationUnitLevelName") String observationUnitLevelName, + @Nullable @QueryValue("observationUnitLevelOrder") String observationUnitLevelOrder, + @Nullable @QueryValue("observationUnitLevelCode") String observationUnitLevelCode, + @Nullable @QueryValue("observationUnitLevelRelationshipName") String observationUnitLevelRelationshipName, + @Nullable @QueryValue("observationUnitLevelRelationshipOrder") String observationUnitLevelRelationshipOrder, + @Nullable @QueryValue("observationUnitLevelRelationshipCode") String observationUnitLevelRelationshipCode, + @Nullable @QueryValue("observationUnitLevelRelationshipDbId") String observationUnitLevelRelationshipDbId) { + return HttpResponse.notFound(); + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIProgramsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIProgramsController.java new file mode 100644 index 000000000..a30dbfbed --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIProgramsController.java @@ -0,0 +1,188 @@ +/* + * 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; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.*; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import lombok.extern.slf4j.Slf4j; +import org.brapi.v2.model.BrAPIIndexPagination; +import org.brapi.v2.model.BrAPIMetadata; +import org.brapi.v2.model.core.BrAPIProgram; +import org.brapi.v2.model.core.response.BrAPIProgramListResponse; +import org.brapi.v2.model.core.response.BrAPIProgramListResponseResult; +import org.brapi.v2.model.core.response.BrAPIProgramSingleResponse; +import org.breedinginsight.api.auth.ProgramSecured; +import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; +import org.breedinginsight.api.auth.SecurityService; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +@Controller("/${micronaut.bi.api.version}") +@Secured(SecurityRule.IS_AUTHENTICATED) +public class BrAPIProgramsController { + + private final SecurityService securityService; + private final ProgramService programService; + + @Inject + public BrAPIProgramsController(SecurityService securityService, ProgramService programService) { + this.securityService = securityService; + this.programService = programService; + } + + /* + TODO + - GET programs + */ + + //START - endpoints at root BrAPI url + @Get(BrapiVersion.BRAPI_V2 + "/programs") + public HttpResponse rootProgramsGet( + @Nullable @QueryValue("abbreviation") String abbreviation, + @Nullable @QueryValue("programType") String programType, + @Nullable @QueryValue("commonCropName") String commonCropName, + @Nullable @QueryValue("programDbId") String programDbId, + @Nullable @QueryValue("programName") String programName, + @Nullable @QueryValue("externalReferenceID") String externalReferenceID, + @Nullable @QueryValue("externalReferenceId") String externalReferenceId, + @Nullable @QueryValue("externalReferenceSource") String externalReferenceSource, + @Nullable @QueryValue("page") Integer page, + @Nullable @QueryValue("pageSize") Integer pageSize) { + + Optional abbreviationOptional = Optional.ofNullable(abbreviation); + Optional programDbIdOptional = Optional.ofNullable(programDbId); + Optional programNameOptional = Optional.ofNullable(programName); + + List programs = programService.getAll(securityService.getUser()).stream().filter(program -> { + boolean matches = abbreviationOptional.map(abbr -> abbr.equals(program.getKey())).orElse(true); + matches = matches && programDbIdOptional.map(id -> id.equals(program.getId().toString())).orElse(true); + return matches && programNameOptional.map(name -> name.equals(program.getName())).orElse(true); + }).map(this::convertToBrAPI).collect(Collectors.toList()); + + return HttpResponse.ok(new BrAPIProgramListResponse().metadata(new BrAPIMetadata().pagination(new BrAPIIndexPagination().currentPage(0) + .totalPages(1) + .totalCount(programs.size()) + .pageSize(programs.size()))) + .result(new BrAPIProgramListResponseResult().data(programs))); + } + + @Post(BrapiVersion.BRAPI_V2 + "/programs") + public HttpResponse rootProgramsPost(@Body List body) { + //DO NOT IMPLEMENT - Users should only be able to create new programs via the DeltaBreed UI + return HttpResponse.notFound(); + } + + @Get(BrapiVersion.BRAPI_V2 + "/programs/{programDbId}") + public HttpResponse rootProgramsProgramDbIdGet(@PathVariable("programDbId") String programDbId) { + HttpResponse brAPIProgramListResponseHttpResponse = this.rootProgramsGet(null, null, null, + programDbId, + null, + null, + null, + null, + null, + null); + Optional program = Optional.ofNullable(brAPIProgramListResponseHttpResponse.body()) + .orElse(new BrAPIProgramListResponse()) + .getResult() + .getData().stream().findFirst(); + + return HttpResponse.ok(new BrAPIProgramSingleResponse().result(program.orElse(null))); + } + + @Put(BrapiVersion.BRAPI_V2 + "/programs/{programDbId}") + public HttpResponse rootProgramsProgramDbIdPut(@PathVariable("programDbId") String programDbId, @Body BrAPIProgram body) { + //DO NOT IMPLEMENT - Users should only be able to update programs via the DeltaBreed UI + return HttpResponse.notFound(); + } + //END - endpoints at root BrAPI url + + + //START - endpoints for within the context of a program + @Get("/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/programs") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse programsGet(@PathVariable("programId") UUID programId, + @QueryValue("abbreviation") Optional abbreviation, + @QueryValue("programType") Optional programType, + @QueryValue("commonCropName") Optional commonCropName, + @QueryValue("programDbId") Optional programDbId, + @QueryValue("programName") Optional programName, + @QueryValue("externalReferenceID") Optional externalReferenceID, + @QueryValue("externalReferenceId") Optional externalReferenceId, + @QueryValue("externalReferenceSource") Optional externalReferenceSource, + @QueryValue("page") Optional page, + @QueryValue("pageSize") Optional pageSize) { + + List programs = programService.getById(programId).stream().filter(program -> { + boolean matches = abbreviation.map(abbr -> abbr.equals(program.getKey())).orElse(true); + matches = matches && programDbId.map(id -> id.equals(program.getId().toString())).orElse(true); + return matches && programName.map(name -> name.equals(program.getName())).orElse(true); + }).map(this::convertToBrAPI).collect(Collectors.toList()); + + return HttpResponse.ok(new BrAPIProgramListResponse().metadata(new BrAPIMetadata().pagination(new BrAPIIndexPagination().currentPage(0) + .totalPages(1) + .totalCount(programs.size()) + .pageSize(programs.size()))) + .result(new BrAPIProgramListResponseResult().data(programs))); + } + + @Post("/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/programs") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse programsPost(@PathVariable("programId") UUID programId, @Body List body) { + //DO NOT IMPLEMENT - Users should only be able to create new programs via the DeltaBreed UI + return HttpResponse.notFound(); + } + + @Get("/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/programs/{programDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse programsProgramDbIdGet(@PathVariable("programId") UUID programId, @PathVariable("programDbId") String programDbId) { + Optional program = programService.getById(programId) + .stream() + .filter(p -> programDbId.equals(programId.toString())) + .map(this::convertToBrAPI) + .findFirst(); + + return HttpResponse.ok(new BrAPIProgramSingleResponse().result(program.orElse(null))); + } + + @Put("/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/programs/{programDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse programsProgramDbIdPut(@PathVariable("programId") UUID programId, @PathVariable("programDbId") String programDbId, @Body BrAPIProgram body) { + //DO NOT IMPLEMENT - Users should only be able to update programs via the DeltaBreed UI + return HttpResponse.notFound(); + } + //END - endpoints for within the context of a program + + private BrAPIProgram convertToBrAPI(Program program) { + return new BrAPIProgram().programName(program.getName()) + .programDbId(program.getId().toString()) + .abbreviation(program.getKey()) + .commonCropName(program.getSpecies().getCommonName()); + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIStudiesController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIStudiesController.java new file mode 100644 index 000000000..0382ecdb0 --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIStudiesController.java @@ -0,0 +1,151 @@ +/* + * 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; + +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.*; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import lombok.extern.slf4j.Slf4j; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.v2.model.BrAPIMetadata; +import org.brapi.v2.model.BrAPIStatus; +import org.brapi.v2.model.core.BrAPIStudy; +import org.brapi.v2.model.core.response.BrAPIStudySingleResponse; +import org.breedinginsight.api.auth.ProgramSecured; +import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; +import org.breedinginsight.api.model.v1.request.query.SearchRequest; +import org.breedinginsight.api.model.v1.response.DataResponse; +import org.breedinginsight.api.model.v1.response.Response; +import org.breedinginsight.api.model.v1.validators.QueryValid; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; +import org.breedinginsight.brapi.v2.model.request.query.StudyQuery; +import org.breedinginsight.brapi.v2.services.BrAPIStudyService; +import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.utilities.Utilities; +import org.breedinginsight.utilities.response.ResponseUtils; +import org.breedinginsight.utilities.response.mappers.StudyQueryMapper; + +import javax.inject.Inject; +import javax.validation.Valid; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +@Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class BrAPIStudiesController { + + private final String referenceSource; + private final BrAPIStudyService studyService; + private final StudyQueryMapper studyQueryMapper; + private final ProgramService programService; + + + @Inject + public BrAPIStudiesController(BrAPIStudyService studyService, StudyQueryMapper studyQueryMapper, @Property(name = "brapi.server.reference-source") String referenceSource, ProgramService programService) { + this.studyService = studyService; + this.studyQueryMapper = studyQueryMapper; + this.referenceSource = referenceSource; + this.programService = programService; + } + + @Get("/studies{?queryParams*}") + @Produces(MediaType.APPLICATION_JSON) + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse>>> getStudies( + @PathVariable("programId") UUID programId, + @QueryValue @QueryValid(using = StudyQueryMapper.class) @Valid StudyQuery queryParams) { + try { + log.debug("fetching studies for program: " + programId); + + List studies = studyService.getStudies(programId) + .stream() + .peek(this::setDbIds) + .collect(Collectors.toList()); + queryParams.setSortField(studyQueryMapper.getDefaultSortField()); + queryParams.setSortOrder(studyQueryMapper.getDefaultSortOrder()); + SearchRequest searchRequest = queryParams.constructSearchRequest(); + return ResponseUtils.getBrapiQueryResponse(studies, studyQueryMapper, queryParams, searchRequest); + } catch (ApiException e) { + log.info(e.getMessage(), e); + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error retrieving study"); + } catch (IllegalArgumentException e) { + log.info(e.getMessage(), e); + return HttpResponse.status(HttpStatus.UNPROCESSABLE_ENTITY, "Error parsing requested date format"); + } + } + + @Post("/studies") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse studiesPost(@PathVariable("programId") UUID programId, @Body List body) { + //DO NOT IMPLEMENT - Users are only able to create new studies via the DeltaBreed UI + return HttpResponse.notFound(); + } + + @Get("/studies/{studyDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse studiesStudyDbIdGet(@PathVariable("programId") UUID programId, @PathVariable("studyDbId") String environmentId) { + Optional program = programService.getById(programId); + if(program.isEmpty()) { + log.warn("program id: " + programId + " not found"); + return HttpResponse.notFound(); + } + try { + Optional study = studyService.getStudyByEnvironmentId(program.get(), UUID.fromString(environmentId)); + if(study.isPresent()) { + setDbIds(study.get()); + return HttpResponse.ok(new BrAPIStudySingleResponse().result(study.get())); + } else { + log.warn("studyDbId: " + environmentId + " not found"); + return HttpResponse.notFound(); + } + } catch (ApiException e) { + log.error(Utilities.generateApiExceptionLogMessage(e), e); + return HttpResponse.serverError(new BrAPIStudySingleResponse().metadata(new BrAPIMetadata().addStatusItem(new BrAPIStatus().message("Error fetching study") + .messageType(BrAPIStatus.MessageTypeEnum.ERROR)))); + } + } + + @Put("/studies/{studyDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse studiesStudyDbIdPut(@PathVariable("programId") UUID programId, @PathVariable("studyDbId") String studyDbId, + @Body BrAPIStudy body) { + //DO NOT IMPLEMENT - Users are only able to update studies via the DeltaBreed UI + return HttpResponse.notFound(); + } + + private void setDbIds(BrAPIStudy study) { + study.studyDbId(Utilities.getExternalReference(study.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES)) + .orElseThrow(() -> new IllegalStateException("No BI external reference found")) + .getReferenceID()); + + study.trialDbId(Utilities.getExternalReference(study.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.TRIALS)) + .orElseThrow(() -> new IllegalStateException("No BI external reference found")) + .getReferenceID()); + + //TODO update locationDbId + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPITrialsController.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPITrialsController.java new file mode 100644 index 000000000..666115efa --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPITrialsController.java @@ -0,0 +1,120 @@ +package org.breedinginsight.brapi.v2; + +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.*; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import lombok.extern.slf4j.Slf4j; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.v2.model.core.BrAPITrial; +import org.brapi.v2.model.core.response.BrAPITrialSingleResponse; +import org.breedinginsight.api.auth.ProgramSecured; +import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; +import org.breedinginsight.api.model.v1.request.query.SearchRequest; +import org.breedinginsight.api.model.v1.response.DataResponse; +import org.breedinginsight.api.model.v1.response.Response; +import org.breedinginsight.api.model.v1.validators.QueryValid; +import org.breedinginsight.brapi.v1.controller.BrapiVersion; +import org.breedinginsight.brapi.v2.model.request.query.ExperimentQuery; +import org.breedinginsight.brapi.v2.services.BrAPITrialService; +import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; +import org.breedinginsight.services.exceptions.DoesNotExistException; +import org.breedinginsight.utilities.Utilities; +import org.breedinginsight.utilities.response.ResponseUtils; +import org.breedinginsight.utilities.response.mappers.ExperimentQueryMapper; + +import javax.inject.Inject; +import javax.validation.Valid; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +@Controller("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class BrAPITrialsController { + + private final String referenceSource; + private final BrAPITrialService experimentService; + private final ExperimentQueryMapper experimentQueryMapper; + + @Inject + public BrAPITrialsController(BrAPITrialService experimentService, ExperimentQueryMapper experimentQueryMapper, @Property(name = "brapi.server.reference-source") String referenceSource) { + this.experimentService = experimentService; + this.experimentQueryMapper = experimentQueryMapper; + this.referenceSource = referenceSource; + } + + @Get("/trials{?queryParams*}") + @Produces(MediaType.APPLICATION_JSON) + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse>>> getExperiments( + @PathVariable("programId") UUID programId, + @QueryValue @QueryValid(using = ExperimentQueryMapper.class) @Valid ExperimentQuery queryParams) { + try { + log.debug("fetching trials for program: " + programId); + + List experiments = experimentService.getExperiments(programId).stream().peek(this::setDbIds).collect(Collectors.toList()); + SearchRequest searchRequest = queryParams.constructSearchRequest(); + return ResponseUtils.getBrapiQueryResponse(experiments, experimentQueryMapper, queryParams, searchRequest); + } catch (ApiException e) { + log.info(e.getMessage(), e); + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error retrieving experiments"); + } catch (DoesNotExistException e) { + log.info(e.getMessage(), e); + return HttpResponse.status(HttpStatus.NOT_FOUND, e.getMessage()); + } + } + + @Get("/trials/{trialId}") + @Produces(MediaType.APPLICATION_JSON) + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse getExperimentById( + @PathVariable("programId") UUID programId, + @PathVariable("trialId") UUID trialId, + @QueryValue(defaultValue = "false") Boolean stats){ + try { + String logMsg = "fetching trial id:" + trialId +" for program: " + programId; + if(stats){ + logMsg += " with stats"; + } + log.debug(logMsg); + BrAPITrial trial = experimentService.getTrialDataByUUID(programId, trialId, stats); + setDbIds(trial); + return HttpResponse.ok(new BrAPITrialSingleResponse().result(trial)); + } catch (DoesNotExistException e) { + log.info(e.getMessage(), e); + return HttpResponse.status(HttpStatus.NOT_FOUND, e.getMessage()); + } + } + + @Post("/trials") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse trialsPost(@PathVariable("programId") UUID programId, @Body List body) { + //DO NOT IMPLEMENT - Users are only able to create new trials via the DeltaBreed UI + return HttpResponse.notFound(); + } + + + @Put("/trials/{trialDbId}") + @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) + public HttpResponse trialsTrialDbIdPut(@PathVariable("programId") UUID programId, @PathVariable("trialDbId") String trialDbId, @Body BrAPITrial body) { + //DO NOT IMPLEMENT - Users are only able to update trials via the DeltaBreed UI + return HttpResponse.notFound(); + } + + private void setDbIds(BrAPITrial trial) { + trial.trialDbId(Utilities.getExternalReference(trial.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.TRIALS)) + .orElseThrow(() -> new IllegalStateException("No BI external reference found")) + .getReferenceID()); + trial.programDbId(Utilities.getExternalReference(trial.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.PROGRAMS)) + .orElseThrow(() -> new IllegalStateException("No BI external reference found")) + .getReferenceID()); + + //TODO update locationDbId + } + +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/BrAPIV2Controller.java b/src/main/java/org/breedinginsight/brapi/v2/BrAPIV2Controller.java index 702a0ca14..d6e4dde43 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/BrAPIV2Controller.java +++ b/src/main/java/org/breedinginsight/brapi/v2/BrAPIV2Controller.java @@ -28,6 +28,7 @@ import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.brapi.v2.model.core.BrAPIServerInfo; +import org.brapi.v2.model.core.BrAPIService; import org.brapi.v2.model.core.response.BrAPIServerInfoResponse; import org.breedinginsight.api.auth.AuthenticatedUser; import org.breedinginsight.api.auth.ProgramSecured; @@ -40,6 +41,7 @@ import javax.inject.Inject; import java.io.IOException; +import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -58,18 +60,111 @@ public BrAPIV2Controller(SecurityService securityService, ProgramService program } - @Get(BrapiVersion.BRAPI_V2 + "/serverinfo") + @Get("/${micronaut.bi.api.version}" + BrapiVersion.BRAPI_V2 + "/serverinfo") @Produces(MediaType.APPLICATION_JSON) @Secured(SecurityRule.IS_ANONYMOUS) public BrAPIServerInfoResponse serverinfo() { BrAPIServerInfo serverInfo = new BrAPIServerInfo(); + setBrAPIServerInfo(serverInfo); + serverInfo.setServerDescription("BrAPI endpoints are not implemented at the root of this domain. Please make BrAPI calls in the context of a program (ex: https://app.breedinginsight.net/v1/programs/{programId}/brapi/v2)"); + + serverInfo.setCalls( + new ServiceBuilder().versions("2.0", "2.1") + .setBase("serverinfo").GET().build() + ); + + return new BrAPIServerInfoResponse().result(serverInfo); + } + + @Get("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/serverinfo") + @Produces(MediaType.APPLICATION_JSON) + @Secured(SecurityRule.IS_ANONYMOUS) + public BrAPIServerInfoResponse programServerinfo(@PathVariable("programId") UUID programId) { + String programBrAPIBase = String.format("v1/programs/%s%s/", programId, BrapiVersion.BRAPI_V2); + + List programServices = new ServiceBuilder() + .versions("2.0", "2.1") + //CORE + .setBase("serverinfo").GET().build() + .setBase("commoncropnames").GET().build() + .setBase("lists").GET().POST().addPath("{listDbId}").GET().PUT().withSearch() + .setBase("locations").GET().addPath("{locationDbId}").GET().withSearch() + .setBase("people").GET().addPath("{personDbId}").GET().withSearch() + .setBase("programs").GET().addPath("{programDbId}").GET().withSearch() + .setBase("seasons").GET().addPath("{seasonDbId}").GET().build() + .setBase("studies").GET().addPath("{studyDbId}").GET().withSearch() + .setBase("studytypes").GET().build() + .setBase("trials").GET().addPath("{trialDbId}").GET().withSearch() + //GERMPLASM + .setBase("attributes").GET().addPath("{attributeDbId}").GET().setPath("categories").GET().withSearch() + .setBase("attributevalues").GET().addPath("{attributeValueDbId}").GET().withSearch() + .setBase("breedingmethods").GET().addPath("{breedingMethodDbId}").GET().build() + .setBase("crosses").GET().build() + .setBase("plannedcrosses").GET().build() + .setBase("crossingprojects").GET().addPath("{crossingProjectDbId}").GET().build() + .setBase("seedlots").GET().addPath("transactions").GET().setPath("{seedLotDbId}").GET().addPath("transactions").GET().build() + .setBase("germplasm").GET().addPath("{germplasmDbId}").GET().addPath("mcpd").GET().withSearch() + //PHENOTYPING + .setBase("events").GET().build() + .setBase("images").GET().addPath("{imageDbId}").GET().addPath("imagecontent").withSearch() + .setBase("ontologies").GET().build() + .setBase("traits").GET().addPath("{traitDbId}").GET().build() + .setBase("methods").GET().addPath("{methodDbId}").GET().build() + .setBase("scales").GET().addPath("{scaleDbId}").GET().build() + .setBase("variables").GET().addPath("{observationVariableDbId}").GET().withSearch() + .setBase("observationunits").GET().addPath("{observationUnitDbId}").GET().setPath("table").GET().withSearch() + .setBase("observations").GET().addPath("{observationDbId}").GET().setPath("table").GET().withSearch() + .setBase("observationlevels").GET().build() + //GENOTYPING - TODO +// .setBase("calls").GET().withSearch() +// .setBase("callsets").GET().addPath("{callSetDbId}").GET().addPath("calls").GET().withSearch() +// .setBase("maps").GET().addPath("{mapDbId}").GET().addPath("linkagegroups").GET().build() +// .setBase("markerpositions").GET().withSearch() +// .setBase("references").GET().addPath("{referenceDbId}").GET().addPath("bases").GET().withSearch() +// .setBase("referencesets").GET().addPath("{referenceSetDbId}").GET().withSearch() +// .setBase("samples").GET().addPath("{sampleDbId}").GET().withSearch() +// .setBase("variants").GET().addPath("{variantDbId}").GET().addPath("calls").GET().withSearch() +// .setBase("variantsets").GET().addPath("extract").setPath("{variantSetDbId}").GET() +// .addPath("calls").GET().setPath("callsets").GET().setPath("variants").GET().withSearch() +// .setBase("vendor").addPath("specifications").GET().setPath("plates").addPath("{submissionId}").build() +// .setBase("vendor/orders").GET().addPath("{orderId}").addPath("plates").GET().setPath("results").GET().setPath("status").GET().build() + + //V2.0 only + .versions("2.0") + .setBase("germplasm").addPath("{germplasmDbId}").addPath("pedigree").GET().setPath("progeny").GET().build() + .setBase("lists").addPath("{listDbId}").addPath("items").build() +// .setBase("samples").addPath("{sampleDbId}").build() //TODO + //V2.1 only + .versions("2.1") +// .setBase("allelematrix").GET().withSearch() //TODO +// .setBase("calls").build() //TODO + .setBase("delete").addPath("images").setPath("observations").build() + .setBase("lists").addPath("{listDbId}").addPath("data").POST().build() + .setBase("ontologies").addPath("{ontologyDbId}").GET().build() + .setBase("pedigree").GET().withSearch() +// .setBase("plates").GET().addPath("{plateDbId}").GET().withSearch() //TODO +// .setBase("samples").build() //TODO + .build(); + + for(BrAPIService service : programServices) { + service.setService(programBrAPIBase + service.getService()); + } + + BrAPIServerInfo programServerInfo = new BrAPIServerInfo(); + setBrAPIServerInfo(programServerInfo); + programServerInfo.setCalls(programServices); + + return new BrAPIServerInfoResponse().result(programServerInfo); + } + + private void setBrAPIServerInfo(BrAPIServerInfo serverInfo) { serverInfo.setOrganizationName("Breeding Insight"); serverInfo.setServerName("DeltaBreed"); serverInfo.setContactEmail("bidevteam@cornell.edu"); - serverInfo.setOrganizationURL("breedinginsight.org"); - serverInfo.setServerDescription("BrAPI endpoints are not implemented at the root of this domain. Please make BrAPI calls in the context of a program (ex: https://app.breedinginsight.net/v1/programs//brapi/v2)"); - - return new BrAPIServerInfoResponse().result(serverInfo); + serverInfo.setOrganizationURL("https://breedinginsight.org"); + serverInfo.setServerDescription("DeltaBreed - breeding data management system"); + serverInfo.setLocation("Cornell University, Ithaca, NY, USA"); + serverInfo.setDocumentationURL("https://brapi.org/specification"); } @Get("/${micronaut.bi.api.version}/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/{+path}") @@ -154,7 +249,23 @@ private HttpResponse makeCall(Request brapiRequest) { } private HttpUrl getUrl(UUID programId, String path, HttpRequest request) { + var programBrAPIBaseUrl = getProgramBrAPIBaseUrl(programId); + + var requestUrl = HttpUrl.parse(programBrAPIBaseUrl + "/" + path).newBuilder(); + request.getParameters() + .asMap() + .entrySet() + .stream() + .filter(param -> !param.getKey() + .equals("programId")) + .forEach(param -> param.getValue() + .forEach(val -> requestUrl.addQueryParameter(param.getKey(), val))); + + return requestUrl.build(); + } + + private String getProgramBrAPIBaseUrl(UUID programId) { ProgramBrAPIEndpoints programBrAPIEndpoints; try { programBrAPIEndpoints = programService.getBrapiEndpoints(programId); @@ -168,20 +279,7 @@ private HttpUrl getUrl(UUID programId, String path, HttpRequest request) { } var programBrAPIBaseUrl = programBrAPIEndpoints.getCoreUrl().get(); programBrAPIBaseUrl = programBrAPIBaseUrl.endsWith("/") ? programBrAPIBaseUrl.substring(0, programBrAPIBaseUrl.length() - 1) : programBrAPIBaseUrl; - String urlString = programBrAPIBaseUrl.endsWith(BrapiVersion.BRAPI_V2) ? programBrAPIBaseUrl : programBrAPIBaseUrl + BrapiVersion.BRAPI_V2; - var requestUrl = HttpUrl.parse(urlString + "/" + path) - .newBuilder(); - - request.getParameters() - .asMap() - .entrySet() - .stream() - .filter(param -> !param.getKey() - .equals("programId")) - .forEach(param -> param.getValue() - .forEach(val -> requestUrl.addQueryParameter(param.getKey(), val))); - - return requestUrl.build(); + return programBrAPIBaseUrl.endsWith(BrapiVersion.BRAPI_V2) ? programBrAPIBaseUrl : programBrAPIBaseUrl + BrapiVersion.BRAPI_V2; } private void logCall(String path, HttpRequest request) { diff --git a/src/main/java/org/breedinginsight/brapi/v2/ServiceBuilder.java b/src/main/java/org/breedinginsight/brapi/v2/ServiceBuilder.java new file mode 100644 index 000000000..c5a0464a6 --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/ServiceBuilder.java @@ -0,0 +1,80 @@ +package org.breedinginsight.brapi.v2; + +import org.brapi.v2.model.BrAPIWSMIMEDataTypes; +import org.brapi.v2.model.core.BrAPIService; +import org.brapi.v2.model.core.BrAPIService.MethodsEnum; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ServiceBuilder extends ArrayList{ + private static final long serialVersionUID = 1L; + private String path = ""; + private String base = ""; + private List methods = new ArrayList<>(); + private List versions = new ArrayList<>(); + + public ServiceBuilder setBase(String base) { + this.path = base; + this.base = base; + this.methods.clear(); + return this; + } + public ServiceBuilder setPath(String newPath) { + build(); + + String[] pathParts = this.path.split("/"); + String oldPath = pathParts[pathParts.length - 1].replaceAll("\\{", "\\\\{").replaceAll("\\}", "\\\\}"); + this.path = this.path.replaceFirst(oldPath, newPath); + this.methods.clear(); + return this; + } + public ServiceBuilder addPath(String path) { + build(); + this.path = this.path + '/' + path; + this.methods.clear(); + return this; + } + + public ServiceBuilder PUT() { + methods.add(MethodsEnum.PUT); + return this; + } + + public ServiceBuilder POST() { + methods.add(MethodsEnum.POST); + return this; + } + + public ServiceBuilder GET() { + methods.add(MethodsEnum.GET); + return this; + } + + public ServiceBuilder versions(String ... versions) { + this.versions = Arrays.asList(versions); + return this; + } + + public ServiceBuilder build() { + if(path != null && !path.isEmpty() && methods != null && !methods.isEmpty()) { + this.add(buildService(path, methods)); + } + return this; + } + public ServiceBuilder withSearch() { + build(); + this.add(buildService("search/" + base, Arrays.asList(MethodsEnum.POST))); + this.add(buildService("search/" + base + "/{searchResultsDbId}", Arrays.asList(MethodsEnum.GET))); + return this; + } + public BrAPIService buildService(String path, List methods) { + BrAPIService service = new BrAPIService(); + service.addDataTypesItem(BrAPIWSMIMEDataTypes.APPLICATION_JSON); + service.setMethods(new ArrayList<>(methods)); + service.setVersions(new ArrayList<>(this.versions)); + service.setService(path); + return service; + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/StudyController.java b/src/main/java/org/breedinginsight/brapi/v2/StudyController.java deleted file mode 100644 index 29aeb7dd3..000000000 --- a/src/main/java/org/breedinginsight/brapi/v2/StudyController.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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; - -import io.micronaut.http.HttpResponse; -import io.micronaut.http.HttpStatus; -import io.micronaut.http.MediaType; -import io.micronaut.http.annotation.*; -import io.micronaut.security.annotation.Secured; -import io.micronaut.security.rules.SecurityRule; -import lombok.extern.slf4j.Slf4j; -import org.brapi.client.v2.model.exceptions.ApiException; -import org.brapi.v2.model.core.BrAPIStudy; -import org.breedinginsight.api.auth.ProgramSecured; -import org.breedinginsight.api.auth.ProgramSecuredRoleGroup; -import org.breedinginsight.api.model.v1.request.query.SearchRequest; -import org.breedinginsight.api.model.v1.response.DataResponse; -import org.breedinginsight.api.model.v1.response.Response; -import org.breedinginsight.api.model.v1.validators.QueryValid; -import org.breedinginsight.brapi.v1.controller.BrapiVersion; -import org.breedinginsight.brapi.v2.model.request.query.StudyQuery; -import org.breedinginsight.brapi.v2.services.BrAPIStudyService; -import org.breedinginsight.utilities.response.ResponseUtils; -import org.breedinginsight.utilities.response.mappers.StudyQueryMapper; - -import javax.inject.Inject; -import javax.validation.Valid; -import java.util.List; -import java.util.UUID; - -@Slf4j -@Controller("/${micronaut.bi.api.version}") -@Secured(SecurityRule.IS_AUTHENTICATED) -public class StudyController { - - private final BrAPIStudyService studyService; - private final StudyQueryMapper studyQueryMapper; - - - @Inject - public StudyController(BrAPIStudyService studyService, StudyQueryMapper studyQueryMapper) { - this.studyService = studyService; - this.studyQueryMapper = studyQueryMapper; - } - - @Get("/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/studies{?queryParams*}") - @Produces(MediaType.APPLICATION_JSON) - @ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL}) - public HttpResponse>>> getStudies( - @PathVariable("programId") UUID programId, - @QueryValue @QueryValid(using = StudyQueryMapper.class) @Valid StudyQuery queryParams) { - try { - log.debug("fetching studies for program: " + programId); - - List studies = studyService.getStudies(programId); - queryParams.setSortField(studyQueryMapper.getDefaultSortField()); - queryParams.setSortOrder(studyQueryMapper.getDefaultSortOrder()); - SearchRequest searchRequest = queryParams.constructSearchRequest(); - return ResponseUtils.getBrapiQueryResponse(studies, studyQueryMapper, queryParams, searchRequest); - } catch (ApiException e) { - log.info(e.getMessage(), e); - return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR, "Error retrieving study"); - } catch (IllegalArgumentException e) { - log.info(e.getMessage(), e); - return HttpResponse.status(HttpStatus.UNPROCESSABLE_ENTITY, "Error parsing requested date format"); - } - } -} diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPICrossDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPICrossDAO.java similarity index 97% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPICrossDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPICrossDAO.java index 5a895d8ec..edfcfb6c7 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPICrossDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPICrossDAO.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos; +package org.breedinginsight.brapi.v2.dao; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.client.v2.modules.germplasm.CrossesApi; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIListDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java similarity index 91% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIListDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java index cb24fd745..801b9775c 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIListDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIListDAO.java @@ -1,4 +1,21 @@ -package org.breedinginsight.brapps.importer.daos; +/* + * 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 lombok.extern.slf4j.Slf4j; import org.brapi.client.v2.ApiResponse; @@ -14,6 +31,7 @@ import org.brapi.v2.model.core.request.BrAPIListSearchRequest; import org.brapi.v2.model.core.response.*; import org.brapi.v2.model.pheno.BrAPIObservation; +import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPILocationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPILocationDAO.java similarity index 97% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPILocationDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPILocationDAO.java index 2fe7b8b01..e45dadf5c 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPILocationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPILocationDAO.java @@ -15,12 +15,13 @@ * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos; +package org.breedinginsight.brapi.v2.dao; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.client.v2.modules.core.LocationsApi; import org.brapi.v2.model.core.BrAPILocation; import org.brapi.v2.model.core.request.BrAPILocationSearchRequest; +import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java similarity index 98% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java index 9c9ed184b..b52aeff22 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationDAO.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos; +package org.breedinginsight.brapi.v2.dao; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; @@ -26,6 +26,7 @@ import org.brapi.v2.model.pheno.request.BrAPIObservationSearchRequest; import org.brapi.v2.model.pheno.response.BrAPIObservationListResponse; import org.brapi.v2.model.pheno.response.BrAPIObservationSingleResponse; +import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.model.Program; diff --git a/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationUnitDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationUnitDAO.java new file mode 100644 index 000000000..40833dc3e --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationUnitDAO.java @@ -0,0 +1,346 @@ +/* + * 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 com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import io.micronaut.context.annotation.Property; +import io.micronaut.http.server.exceptions.InternalServerException; +import org.apache.commons.lang3.StringUtils; +import org.brapi.client.v2.JSON; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.client.v2.modules.phenotype.ObservationUnitsApi; +import org.brapi.v2.model.germ.BrAPIGermplasm; +import org.brapi.v2.model.pheno.BrAPIObservationTreatment; +import org.brapi.v2.model.pheno.BrAPIObservationUnit; +import org.brapi.v2.model.pheno.BrAPIObservationUnitLevelRelationship; +import org.brapi.v2.model.pheno.request.BrAPIObservationUnitSearchRequest; +import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; +import org.breedinginsight.brapi.v2.services.BrAPIGermplasmService; +import org.breedinginsight.brapps.importer.daos.ImportDAO; +import org.breedinginsight.brapps.importer.model.ImportUpload; +import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; +import org.breedinginsight.daos.ProgramDAO; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.services.brapi.BrAPIEndpointProvider; +import org.breedinginsight.services.exceptions.DoesNotExistException; +import org.breedinginsight.utilities.BrAPIDAOUtil; +import org.breedinginsight.utilities.Utilities; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.validation.constraints.NotNull; +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +@Singleton +public class BrAPIObservationUnitDAO { + private final ProgramDAO programDAO; + private final ImportDAO importDAO; + private final BrAPIDAOUtil brAPIDAOUtil; + private final BrAPIEndpointProvider brAPIEndpointProvider; + private final ProgramService programService; + private final BrAPIGermplasmService germplasmService; + + private final String referenceSource; + + private final Gson gson = new JSON().getGson(); + private final Type treatmentlistType = new TypeToken>(){}.getType(); + + @Inject + public BrAPIObservationUnitDAO(ProgramDAO programDAO, + ImportDAO importDAO, + BrAPIDAOUtil brAPIDAOUtil, + BrAPIEndpointProvider brAPIEndpointProvider, + BrAPIGermplasmService germplasmService, + ProgramService programService, + @Property(name = "brapi.server.reference-source") String referenceSource) { + this.programDAO = programDAO; + this.importDAO = importDAO; + this.brAPIDAOUtil = brAPIDAOUtil; + this.brAPIEndpointProvider = brAPIEndpointProvider; + this.referenceSource = referenceSource; + this.programService = programService; + this.germplasmService = germplasmService; + } + + public List getObservationUnitByName(List observationUnitNames, Program program) throws ApiException { + if(observationUnitNames.isEmpty()) { + return Collections.emptyList(); + } + + BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); + observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram().getProgramDbId())); + observationUnitSearchRequest.observationUnitNames(observationUnitNames); + + return searchObservationUnitsAndProcess(observationUnitSearchRequest, program, false); + } + + /** + * Create observation units, mutates brAPIObservationUnitList + */ + public List createBrAPIObservationUnits(List brAPIObservationUnitList, UUID programId, ImportUpload upload) throws ApiException, DoesNotExistException { + Program program = programService.getById(programId).orElseThrow(() -> new DoesNotExistException("Program id does not exist")); + ObservationUnitsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationUnitsApi.class); + preprocessObservationUnits(brAPIObservationUnitList); + List ous = brAPIDAOUtil.post(brAPIObservationUnitList, upload, api::observationunitsPost, importDAO::update); + processObservationUnits(program, ous, false); + return ous; + } + + public BrAPIObservationUnit updateBrAPIObservationUnit(String ouDbId, BrAPIObservationUnit ou, UUID programId) { + ObservationUnitsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationUnitsApi.class); + BrAPIObservationUnit updatedOu = null; + try { + if (ou != null && !ouDbId.isBlank()) { + updatedOu = brAPIDAOUtil.put(ouDbId, ou, api::observationunitsObservationUnitDbIdPut); + } + return updatedOu; + } catch (Exception e) { + throw new InternalServerException("Unknown error has occurred: " + e.getMessage(), e); + } + } + + public List getObservationUnitsById(Collection observationUnitExternalIds, Program program) throws ApiException { + if(observationUnitExternalIds.isEmpty()) { + return Collections.emptyList(); + } + + BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); + observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram() + .getProgramDbId())); + observationUnitSearchRequest.externalReferenceIDs(new ArrayList<>(observationUnitExternalIds)); + observationUnitSearchRequest.externalReferenceSources(List.of(String.format("%s/%s", referenceSource, ExternalReferenceSource.OBSERVATION_UNITS.getName()))); + + return searchObservationUnitsAndProcess(observationUnitSearchRequest, program, false); + } + + public List getObservationUnitsForStudyDbId(@NotNull String studyDbId, Program program) throws ApiException { + BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); + observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram() + .getProgramDbId())); + observationUnitSearchRequest.studyDbIds(List.of(studyDbId)); + + return searchObservationUnitsAndProcess(observationUnitSearchRequest, program, false); + } + + public List getObservationUnitsForTrialDbId(@NotNull UUID programId, @NotNull String trialDbId) throws ApiException, DoesNotExistException { + return getObservationUnitsForTrialDbId(programId, trialDbId, false); + } + + public List getObservationUnitsForTrialDbId(@NotNull UUID programId, @NotNull String trialDbId, boolean withGID) throws ApiException, DoesNotExistException { + Program program = programService.getById(programId).orElseThrow(() -> new DoesNotExistException("Program id does not exist")); + + BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); + observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram() + .getProgramDbId())); + observationUnitSearchRequest.trialDbIds(List.of(trialDbId)); + + return searchObservationUnitsAndProcess(observationUnitSearchRequest, program, withGID); + } + + public List getObservationUnitsForDataset(@NotNull String datasetId, @NotNull Program program) throws ApiException { + String datasetReferenceSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.DATASET); + BrAPIObservationUnitSearchRequest ouSearchRequest = new BrAPIObservationUnitSearchRequest(); + ouSearchRequest.programDbIds(List.of(program.getBrapiProgram().getProgramDbId())); + ouSearchRequest.externalReferenceSources(List.of(datasetReferenceSource)); + ouSearchRequest.externalReferenceIDs(List.of(datasetId)); + return searchObservationUnitsAndProcess(ouSearchRequest, program, true); + } + + public List getObservationUnits(Program program, + Optional observationUnitId, + Optional observationUnitName, + Optional locationDbId, + Optional seasonDbId, + Optional includeObservations, + Optional observationUnitLevelName, + Optional observationUnitLevelOrder, + Optional observationUnitLevelCode, + Optional observationUnitLevelRelationshipName, + Optional observationUnitLevelRelationshipOrder, + Optional observationUnitLevelRelationshipCode, + Optional observationUnitLevelRelationshipDbId, + Optional commonCropName, + Optional experimentId, + Optional environmentId, + Optional germplasmId +// , Integer page, +// Integer pageSize + ) throws ApiException { + BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); + observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram() + .getProgramDbId())); + //TODO add pagination support +// .page(page) +// .pageSize(pageSize); + + List xrefIds = new ArrayList<>(); + List xrefSources = new ArrayList<>(); + BrAPIObservationUnitLevelRelationship level = new BrAPIObservationUnitLevelRelationship(); + AtomicBoolean levelFilter = new AtomicBoolean(false); + BrAPIObservationUnitLevelRelationship relationship = new BrAPIObservationUnitLevelRelationship(); + AtomicBoolean relationshipFilter = new AtomicBoolean(false); + + observationUnitId.ifPresent(ouId -> addXRefFilter(ouId, ExternalReferenceSource.OBSERVATION_UNITS, xrefIds, xrefSources)); + observationUnitName.ifPresent(name -> observationUnitSearchRequest.setObservationUnitNames(List.of(Utilities.appendProgramKey(name, program.getKey())))); + locationDbId.ifPresent(dbid -> observationUnitSearchRequest.setLocationDbIds(List.of(dbid))); + seasonDbId.ifPresent(dbid -> observationUnitSearchRequest.setSeasonDbIds(List.of(dbid))); + includeObservations.ifPresent(observationUnitSearchRequest::includeObservations); + addLevelFilter(observationUnitLevelName, observationUnitLevelOrder, observationUnitLevelCode, level, levelFilter); + addLevelFilter(observationUnitLevelRelationshipName, observationUnitLevelRelationshipOrder, observationUnitLevelRelationshipCode, relationship, relationshipFilter); + experimentId.ifPresent(expId -> addXRefFilter(expId, ExternalReferenceSource.TRIALS, xrefIds, xrefSources)); + environmentId.ifPresent(envId -> addXRefFilter(envId, ExternalReferenceSource.STUDIES, xrefIds, xrefSources)); +// germplasmId.ifPresent(germId -> { +// xrefIds.add(germId); +// xrefSources.add(Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.)); +// }); + + if(!xrefIds.isEmpty()) { + observationUnitSearchRequest.externalReferenceIDs(xrefIds); + } + if(!xrefSources.isEmpty()) { + observationUnitSearchRequest.externalReferenceSources(xrefSources); + } + + return searchObservationUnitsAndProcess(observationUnitSearchRequest, program, true).stream().filter(ou -> { + //xref search does an OR, so we need to convert the searching for ouId/expId/envId to be an AND + boolean matches = observationUnitId.map(id -> id.equals(Utilities.getExternalReference(ou.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.OBSERVATION_UNITS)) + .get() + .getReferenceID())) + .orElse(true); + matches = matches && experimentId.map(id -> id.equals(Utilities.getExternalReference(ou.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.TRIALS)) + .get() + .getReferenceID())) + .orElse(true); + matches = matches && environmentId.map(id -> id.equals(Utilities.getExternalReference(ou.getExternalReferences(), Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES)) + .get() + .getReferenceID())) + .orElse(true); + + //adding filter for germplasmDbId because we can't easily search that in the stored data object + return matches && germplasmId.map(id -> id.equals(ou.getAdditionalInfo().get(BrAPIAdditionalInfoFields.GERMPLASM_UUID).getAsString())).orElse(true); + }).collect(Collectors.toList()); + } + + private void addXRefFilter(String ouId, ExternalReferenceSource externalReferenceSource, List xrefIds, List xrefSources) { + xrefIds.add(ouId); + xrefSources.add(Utilities.generateReferenceSource(referenceSource, externalReferenceSource)); + } + + private void addLevelFilter(Optional observationUnitLevelName, Optional observationUnitLevelOrder, Optional observationUnitLevelCode, BrAPIObservationUnitLevelRelationship level, AtomicBoolean levelFilter) { + observationUnitLevelName.ifPresent(name -> { + levelFilter.set(true); + level.setLevelName(name); + }); + observationUnitLevelOrder.ifPresent(order -> { + levelFilter.set(true); + level.setLevelOrder(order); + }); + observationUnitLevelCode.ifPresent(code -> { + levelFilter.set(true); + level.setLevelCode(code); + }); + } + + + /** + * Perform observation unit search and process returned observation units to handle any modifications to the data + * to be returned by bi-api + */ + private List searchObservationUnitsAndProcess(BrAPIObservationUnitSearchRequest request, Program program, boolean withGID) throws ApiException { + + ObservationUnitsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(program.getId()), ObservationUnitsApi.class); + List brapiObservationUnits = brAPIDAOUtil.search(api::searchObservationunitsPost, + api::searchObservationunitsSearchResultsDbIdGet, + request); + + processObservationUnits(program, brapiObservationUnits, withGID); + return brapiObservationUnits; + } + + private void processObservationUnits(Program program, List brapiObservationUnits, boolean withGID) throws ApiException { + + HashMap germplasmByDbId = new HashMap<>(); + if( withGID ){ + // Load germplasm for program into map. + // TODO: if we use redis search, that may be more efficient than loading all germplasm for the program. + this.germplasmService.getGermplasm(program.getId()).forEach((germplasm -> germplasmByDbId.put(germplasm.getGermplasmDbId(), germplasm))); + } + + // if has treatments in additionalInfo, copy to treatments property + for (BrAPIObservationUnit ou : brapiObservationUnits) { + JsonObject additionalInfo = ou.getAdditionalInfo(); + if (additionalInfo != null) { + JsonElement treatmentsElement = additionalInfo.get(BrAPIAdditionalInfoFields.TREATMENTS); + if (treatmentsElement != null) { + List treatments = gson.fromJson(treatmentsElement, treatmentlistType); + ou.setTreatments(treatments); + } + if( withGID ){ + BrAPIGermplasm germplasm = germplasmByDbId.get(ou.getGermplasmDbId()); + ou.putAdditionalInfoItem(BrAPIAdditionalInfoFields.GID, germplasm.getAccessionNumber()); + ou.putAdditionalInfoItem(BrAPIAdditionalInfoFields.GERMPLASM_UUID, + Utilities.getExternalReference(germplasm.getExternalReferences(), referenceSource) + .orElseThrow(() -> new IllegalStateException("Germplasm UUID not found")) + .getReferenceID()); + } + } + ou.setObservationUnitName(Utilities.removeProgramKeyAndUnknownAdditionalData(ou.getObservationUnitName(), program.getKey())); + if(StringUtils.isNotBlank(ou.getGermplasmName())) { + ou.setGermplasmName(Utilities.removeProgramKeyAndUnknownAdditionalData(ou.getGermplasmName(), program.getKey())); + } + if(StringUtils.isNotBlank(ou.getLocationName())) { + ou.setLocationName(Utilities.removeProgramKey(ou.getLocationName(), program.getKey())); + } + if(StringUtils.isNotBlank(ou.getProgramName())) { + ou.setProgramName(ou.getProgramName().replaceAll("\\(" + program.getKey() + "\\)", "").trim()); + } + if(StringUtils.isNotBlank(ou.getTrialName())) { + ou.setTrialName(Utilities.removeProgramKey(ou.getTrialName(), program.getKey())); + } + if(StringUtils.isNotBlank(ou.getStudyName())) { + ou.setStudyName(Utilities.removeProgramKeyAndUnknownAdditionalData(ou.getStudyName(), program.getKey())); + } + if (ou.getObservationUnitPosition() != null + && ou.getObservationUnitPosition().getObservationLevel() != null + && StringUtils.isNotBlank(ou.getObservationUnitPosition().getObservationLevel().getLevelCode())) { + ou.getObservationUnitPosition() + .getObservationLevel() + .setLevelCode(Utilities.removeProgramKeyAndUnknownAdditionalData(ou.getObservationUnitPosition() + .getObservationLevel() + .getLevelCode(), program.getKey())); + } + } + } + + private void preprocessObservationUnits(List brapiObservationUnits) { + // add treatments to additional info + for (BrAPIObservationUnit obsUnit : brapiObservationUnits) { + List treatments = obsUnit.getTreatments(); + if (treatments != null) { + obsUnit.putAdditionalInfoItem(BrAPIAdditionalInfoFields.TREATMENTS, treatments); + } + } + } +} diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationVariableDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java similarity index 97% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationVariableDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java index 86731a0f2..0de5c19ed 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationVariableDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIObservationVariableDAO.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos; +package org.breedinginsight.brapi.v2.dao; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.client.v2.modules.phenotype.ObservationVariablesApi; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIProgramDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIProgramDAO.java similarity index 98% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIProgramDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIProgramDAO.java index 103585849..965de1cab 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIProgramDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIProgramDAO.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos; +package org.breedinginsight.brapi.v2.dao; import io.micronaut.context.annotation.Property; import org.brapi.client.v2.ApiResponse; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPISeasonDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPISeasonDAO.java similarity index 80% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPISeasonDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPISeasonDAO.java index 0ca2600b8..8ce77e1e9 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPISeasonDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPISeasonDAO.java @@ -1,4 +1,21 @@ -package org.breedinginsight.brapps.importer.daos; +/* + * 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 lombok.extern.slf4j.Slf4j; import org.brapi.client.v2.ApiResponse; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIStudyDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAO.java similarity index 92% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIStudyDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAO.java index cae09d9fb..2901baa4e 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIStudyDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAO.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos; +package org.breedinginsight.brapi.v2.dao; import com.google.gson.JsonObject; import io.micronaut.context.annotation.Property; @@ -26,6 +26,7 @@ import org.brapi.v2.model.BrAPIExternalReference; import org.brapi.v2.model.core.BrAPIStudy; import org.brapi.v2.model.core.request.BrAPIStudySearchRequest; +import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; import org.breedinginsight.daos.ProgramDAO; @@ -151,6 +152,15 @@ public List getStudiesByExperimentID(@NotNull UUID experimentID, Pro ); } + public List getStudiesByEnvironmentIds(@NotNull Collection environmentIds, Program program ) throws ApiException { + return programStudyCache.get(program.getId()) + .entrySet() + .stream() + .filter(entry -> environmentIds.contains(UUID.fromString(entry.getKey()))) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + } + public List createBrAPIStudies(List brAPIStudyList, UUID programId, ImportUpload upload) throws ApiException { StudiesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), StudiesApi.class); List createdStudies = new ArrayList<>(); @@ -208,6 +218,12 @@ public Optional getStudyByDbId(String studyDbId, Program program) th return Utilities.getSingleOptional(studies); } + public Optional getStudyByEnvironmentId(UUID environmentId, Program program) throws ApiException { + List studies = getStudiesByEnvironmentIds(List.of(environmentId), program); + + return Utilities.getSingleOptional(studies); + } + /** * Process study into a format for display diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPITrialDAO.java b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPITrialDAO.java similarity index 63% rename from src/main/java/org/breedinginsight/brapps/importer/daos/BrAPITrialDAO.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/BrAPITrialDAO.java index 55e35f6e8..83ee3f47b 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPITrialDAO.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/BrAPITrialDAO.java @@ -1,4 +1,21 @@ -package org.breedinginsight.brapps.importer.daos; +/* + * 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 org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.core.BrAPITrial; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/impl/BrAPITrialDAOImpl.java b/src/main/java/org/breedinginsight/brapi/v2/dao/impl/BrAPITrialDAOImpl.java similarity index 99% rename from src/main/java/org/breedinginsight/brapps/importer/daos/impl/BrAPITrialDAOImpl.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/impl/BrAPITrialDAOImpl.java index f26d039ba..a60008dcd 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/impl/BrAPITrialDAOImpl.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/impl/BrAPITrialDAOImpl.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos.impl; +package org.breedinginsight.brapi.v2.dao.impl; import io.micronaut.context.annotation.Context; import io.micronaut.context.annotation.Property; @@ -26,7 +26,7 @@ import org.brapi.v2.model.BrAPIExternalReference; import org.brapi.v2.model.core.BrAPITrial; import org.brapi.v2.model.core.request.BrAPITrialSearchRequest; -import org.breedinginsight.brapps.importer.daos.BrAPITrialDAO; +import org.breedinginsight.brapi.v2.dao.BrAPITrialDAO; import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/impl/ImportDAOImpl.java b/src/main/java/org/breedinginsight/brapi/v2/dao/impl/ImportDAOImpl.java similarity index 99% rename from src/main/java/org/breedinginsight/brapps/importer/daos/impl/ImportDAOImpl.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/impl/ImportDAOImpl.java index 5b28a35db..7807d573d 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/impl/ImportDAOImpl.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/impl/ImportDAOImpl.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos.impl; +package org.breedinginsight.brapi.v2.dao.impl; import io.micronaut.http.server.exceptions.InternalServerException; import org.breedinginsight.brapps.importer.daos.ImportDAO; diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/impl/ImportMappingDAOImpl.java b/src/main/java/org/breedinginsight/brapi/v2/dao/impl/ImportMappingDAOImpl.java similarity index 99% rename from src/main/java/org/breedinginsight/brapps/importer/daos/impl/ImportMappingDAOImpl.java rename to src/main/java/org/breedinginsight/brapi/v2/dao/impl/ImportMappingDAOImpl.java index b0b3f7f2e..a7f1384f4 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/impl/ImportMappingDAOImpl.java +++ b/src/main/java/org/breedinginsight/brapi/v2/dao/impl/ImportMappingDAOImpl.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.breedinginsight.brapps.importer.daos.impl; +package org.breedinginsight.brapi.v2.dao.impl; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java index cfd44f64b..b64a88c23 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIGermplasmService.java @@ -12,7 +12,7 @@ import org.brapi.v2.model.germ.BrAPIGermplasm; import org.brapi.v2.model.germ.BrAPIGermplasmSynonyms; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; -import org.breedinginsight.brapps.importer.daos.BrAPIListDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; import org.breedinginsight.brapps.importer.model.exports.FileType; import org.breedinginsight.model.Column; import org.breedinginsight.model.DownloadFile; diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIListService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIListService.java index 8ae42f92c..3353ac17c 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIListService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIListService.java @@ -9,7 +9,7 @@ import org.brapi.v2.model.core.request.BrAPIListSearchRequest; import org.brapi.v2.model.core.response.BrAPIListsSingleResponse; import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; -import org.breedinginsight.brapps.importer.daos.*; +import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; import org.breedinginsight.model.Program; import org.breedinginsight.services.exceptions.DoesNotExistException; @@ -17,10 +17,8 @@ import javax.inject.Inject; import javax.inject.Singleton; -import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.UUID; import java.util.stream.Collectors; @Slf4j diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java new file mode 100644 index 000000000..f9742fb74 --- /dev/null +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationVariableService.java @@ -0,0 +1,236 @@ +/* + * 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.services; + +import io.micronaut.context.annotation.Property; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.v2.model.BrAPIOntologyReference; +import org.brapi.v2.model.core.BrAPIStudy; +import org.brapi.v2.model.core.BrAPITrial; +import org.brapi.v2.model.pheno.*; +import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; +import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; +import org.breedinginsight.model.*; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.services.exceptions.DoesNotExistException; +import org.breedinginsight.utilities.Utilities; +import org.jetbrains.annotations.NotNull; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@Singleton +public class BrAPIObservationVariableService { + private final ProgramService programService; + private final BrAPITrialService trialService; + private final String referenceSource; + + @Inject + public BrAPIObservationVariableService( + ProgramService programService, BrAPITrialService trialService, + @Property(name = "brapi.server.reference-source") String referenceSource) { + this.programService = programService; + this.trialService = trialService; + this.referenceSource = referenceSource; + } + + public List getBrAPIObservationVariablesForExperiment( + UUID programId, + Optional experimentId, + Optional environmentId + ) throws DoesNotExistException, ApiException { + log.debug(String.format("fetching variables for experiment. expId: %s, envId: %s ", + experimentId.orElse(""), environmentId.orElse(""))); + Optional program = programService.getById(programId); + if(program.isEmpty()) { + throw new DoesNotExistException("Could not find program: " + programId); + } + UUID expId; + if(experimentId.isPresent()) { + expId = UUID.fromString(experimentId.get()); + } else { + UUID envId = UUID.fromString(environmentId.orElseThrow(() -> new IllegalStateException("no environment id found"))); + BrAPIStudy environment = trialService.getEnvironment(program.get(), envId); + expId = UUID.fromString(Utilities.getExternalReference(environment.getExternalReferences(), + Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.TRIALS)) + .orElseThrow(() -> new IllegalStateException("no external reference found")).getReferenceId()); + } + + BrAPITrial experiment = trialService.getExperiment(program.get(), expId); + if(experiment + .getAdditionalInfo().getAsJsonObject() + .has(BrAPIAdditionalInfoFields.OBSERVATION_DATASET_ID)) { + String obsDatasetId = experiment + .getAdditionalInfo().getAsJsonObject() + .get(BrAPIAdditionalInfoFields.OBSERVATION_DATASET_ID).getAsString(); + return trialService.getDatasetObsVars(obsDatasetId, program.get()); + } + + return new ArrayList<>(); + } + + @NotNull + public List filterVariables(List programTraits, + Optional observationVariableDbId, + Optional observationVariableName, + Optional traitClass, + Optional methodDbId, + Optional methodName, + Optional scaleDbId, + Optional scaleName, + Optional traitDbId, + Optional traitName, + Optional ontologyDbId) throws DoesNotExistException { + log.debug("filtering variables:\n" + + "observationVariableDbId: " + observationVariableDbId + "\n" + + "observationVariableName: " + observationVariableName + "\n" + + "traitClass: " + traitClass + "\n" + + "methodDbId: " + methodDbId + "\n" + + "methodName: " + methodName + "\n" + + "scaleDbId: " + scaleDbId + "\n" + + "scaleName: " + scaleName + "\n" + + "traitDbId: " + traitDbId + "\n" + + "traitName: " + traitName + "\n" + + "ontologyDbId: " + ontologyDbId); + + return programTraits.stream() + .filter(trait -> { + boolean matches = true; + + Map, String>> filterParams = new HashMap<>(); + filterParams.put("observationVariableDbId", + Pair.of(observationVariableDbId, + trait.getId() + .toString())); + filterParams.put("observationVariableName", Pair.of(observationVariableName, trait.getObservationVariableName())); + filterParams.put("traitClass", Pair.of(traitClass, trait.getTraitClass())); + filterParams.put("methodDbId", + Pair.of(methodDbId, + trait.getMethodId() + .toString())); + filterParams.put("methodName", + Pair.of(methodName, + trait.getMethod() + .getDescription())); + filterParams.put("scaleDbId", + Pair.of(scaleDbId, + trait.getScale() + .getId() + .toString())); + filterParams.put("scaleName", + Pair.of(scaleName, + trait.getScale() + .getScaleName())); + filterParams.put("traitDbId", + Pair.of(traitDbId, + trait.getId() + .toString())); + filterParams.put("traitName", Pair.of(traitName, trait.getObservationVariableName())); + filterParams.put("ontologyDbId", + Pair.of(ontologyDbId, + trait.getProgramOntologyId() + .toString())); + + for (Map.Entry, String>> filter : filterParams.entrySet()) { + if (filter.getValue().getLeft().isPresent()) { + log.debug("filtering traits by: " + filter.getKey()); + matches = StringUtils.equals(filter.getValue() + .getLeft() + .get(), + filter.getValue() + .getRight()); + } + if (!matches) { + break; + } + } + + return matches; + }) + .map(this::convertToBrAPI) + .collect(Collectors.toList()); + } + + public BrAPIObservationVariable convertToBrAPI(Trait trait) { + BrAPIOntologyReference brAPIOntologyReference = new BrAPIOntologyReference().ontologyDbId(trait.getProgramOntologyId() + .toString()); + String status = trait.getActive() ? "active" : "inactive"; + List synonyms = prepSynonyms(trait); + return new BrAPIObservationVariable().observationVariableDbId(trait.getId().toString()) + .observationVariableName(trait.getObservationVariableName()) + .defaultValue(trait.getDefaultValue()) + .status(status) + .synonyms(synonyms) + .trait(new BrAPITrait().ontologyReference(brAPIOntologyReference) + .traitName(trait.getObservationVariableName()) + .traitDbId(trait.getId().toString()) + .entity(trait.getEntity()) + .attribute(trait.getAttribute()) + .status(status) + .synonyms(synonyms)) + .method(new BrAPIMethod().ontologyReference(brAPIOntologyReference) + .methodDbId(trait.getMethod().getId().toString()) + .methodClass(trait.getMethod().getMethodClass()) + .description(trait.getMethod().getDescription()) + .formula(trait.getMethod().getFormula())) + .scale(new BrAPIScale().ontologyReference(brAPIOntologyReference) + .scaleDbId(trait.getScale().getId().toString()) + .scaleName(trait.getScale().getScaleName()) + .dataType(BrAPITraitDataType.fromValue(trait.getScale().getDataType().getLiteral())) + .decimalPlaces(trait.getScale().getDecimalPlaces()) + .validValues(new BrAPIScaleValidValues().max(trait.getScale().getValidValueMax()) + .min(trait.getScale().getValidValueMin()) + .categories(trait.getScale().getCategories()))); + } + + /** + * Create a list of synonyms, and ensure that there the first element matches the name of the trait

+ * This is primarily needed to ensure any system using the first synonym as a display shows the actual name of the ontology term + * @param trait + * @return list of synonyms with at least one value (the name of the trait) + */ + private List prepSynonyms(Trait trait) { + List preppedSynonyms = new ArrayList<>(); + if(trait.getSynonyms() != null) { + preppedSynonyms = trait.getSynonyms(); + int traitNameIdx = -1; + for(int i = 0; i < preppedSynonyms.size(); i++) { + if(preppedSynonyms.get(i).equals(trait.getObservationVariableName())) { + traitNameIdx = i; + break; + } + } + if(traitNameIdx > -1) { + String temp = preppedSynonyms.get(traitNameIdx); + preppedSynonyms.set(traitNameIdx, preppedSynonyms.get(0)); + preppedSynonyms.set(0, temp); + } else { + preppedSynonyms.add(0, trait.getObservationVariableName()); + } + } else { + preppedSynonyms.add(trait.getObservationVariableName()); + } + return preppedSynonyms; + } +} diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIStudyService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIStudyService.java index f2069e5bb..570609d53 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIStudyService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIStudyService.java @@ -17,15 +17,16 @@ package org.breedinginsight.brapi.v2.services; -import io.micronaut.http.server.exceptions.InternalServerException; import lombok.extern.slf4j.Slf4j; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.core.BrAPIStudy; -import org.breedinginsight.brapps.importer.daos.BrAPIStudyDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIStudyDAO; +import org.breedinginsight.model.Program; import javax.inject.Inject; import javax.inject.Singleton; import java.util.List; +import java.util.Optional; import java.util.UUID; @Slf4j @@ -40,11 +41,11 @@ public BrAPIStudyService(BrAPIStudyDAO studyDAO) { } public List getStudies(UUID programId) throws ApiException { - try { - return studyDAO.getStudies(programId); - } catch (ApiException e) { - throw new InternalServerException(e.getMessage(), e); - } + return studyDAO.getStudies(programId); + } + + public Optional getStudyByEnvironmentId(Program program, UUID environmentId) throws ApiException { + return studyDAO.getStudyByEnvironmentId(environmentId, program); } } diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPITrialService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPITrialService.java index 95d34cd32..98c07a23e 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPITrialService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPITrialService.java @@ -14,19 +14,20 @@ import org.brapi.v2.model.pheno.*; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; -import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; +import org.breedinginsight.brapi.v2.dao.*; import org.breedinginsight.brapi.v2.model.request.query.ExperimentExportQuery; -import org.breedinginsight.brapps.importer.daos.*; import org.breedinginsight.brapps.importer.model.exports.FileType; import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation; import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation.Columns; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; import org.breedinginsight.brapps.importer.services.FileMappingUtil; +import org.breedinginsight.dao.db.enums.DataType; import org.breedinginsight.model.BrAPIConstants; import org.breedinginsight.model.Column; import org.breedinginsight.model.DownloadFile; import org.breedinginsight.model.Program; import org.breedinginsight.model.*; +import org.breedinginsight.services.TraitService; import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.services.parsers.experiment.ExperimentFileColumns; import org.breedinginsight.utilities.IntOrderComparator; @@ -54,7 +55,8 @@ public class BrAPITrialService { private final BrAPITrialDAO trialDAO; private final BrAPIObservationDAO observationDAO; private final BrAPIListDAO listDAO; - private final BrAPIObservationVariableDAO obsVarDAO; + + private final TraitService traitService; private final BrAPIStudyDAO studyDAO; private final BrAPISeasonDAO seasonDAO; private final BrAPIObservationUnitDAO ouDAO; @@ -67,7 +69,7 @@ public BrAPITrialService(@Property(name = "brapi.server.reference-source") Strin BrAPITrialDAO trialDAO, BrAPIObservationDAO observationDAO, BrAPIListDAO listDAO, - BrAPIObservationVariableDAO obsVarDAO, + TraitService traitService, BrAPIStudyDAO studyDAO, BrAPISeasonDAO seasonDAO, BrAPIObservationUnitDAO ouDAO, @@ -78,7 +80,7 @@ public BrAPITrialService(@Property(name = "brapi.server.reference-source") Strin this.trialDAO = trialDAO; this.observationDAO = observationDAO; this.listDAO = listDAO; - this.obsVarDAO = obsVarDAO; + this.traitService = traitService; this.studyDAO = studyDAO; this.seasonDAO = seasonDAO; this.ouDAO = ouDAO; @@ -123,7 +125,7 @@ public DownloadFile exportObservations( DownloadFile downloadFile; boolean isDataset = false; List dataset = new ArrayList<>(); - List obsVars = new ArrayList<>(); + List obsVars = new ArrayList<>(); Map> rowByOUId = new HashMap<>(); Map studyByDbId = new HashMap<>(); Map studyDbIdByOUId = new HashMap<>(); @@ -301,9 +303,9 @@ public Dataset getDatasetData(Program program, UUID experimentId, UUID datsetId, log.debug("fetching observationUnits for dataset: " + datsetId); List datasetOUs = ouDAO.getObservationUnitsForDataset(datsetId.toString(), program); log.debug("fetching dataset variables dataset: " + datsetId); - List datasetObsVars = getDatasetObsVars(datsetId.toString(), program); + List datasetObsVars = getDatasetObsVars(datsetId.toString(), program); List ouDbIds = datasetOUs.stream().map(BrAPIObservationUnit::getObservationUnitDbId).collect(Collectors.toList()); - List obsVarDbIds = datasetObsVars.stream().map(BrAPIObservationVariable::getObservationVariableDbId).collect(Collectors.toList()); + List obsVarDbIds = datasetObsVars.stream().map(Trait::getObservationVariableDbId).collect(Collectors.toList()); log.debug("fetching observations for dataset: " + datsetId); List data = observationDAO.getObservationsByObservationUnitsAndVariables(ouDbIds, obsVarDbIds, program); log.debug("building dataset object for dataset: " + datsetId); @@ -333,10 +335,10 @@ private void addBrAPIObsToRecords( Map studyByDbId, Map> rowByOUId, boolean includeTimestamp, - List obsVars, + List obsVars, Map studyDbIdByOUId, Map programGermplasmByDbId) throws ApiException, DoesNotExistException { - Map varByDbId = new HashMap<>(); + Map varByDbId = new HashMap<>(); obsVars.forEach(var -> varByDbId.put(var.getObservationVariableDbId(), var)); for (BrAPIObservation obs: dataset) { @@ -345,7 +347,7 @@ private void addBrAPIObsToRecords( String ouId = getOUId(ou); // get observation variable for BrAPI observation - BrAPIObservationVariable var = varByDbId.get(obs.getObservationVariableDbId()); + Trait var = varByDbId.get(obs.getObservationVariableDbId()); // if there is a row with that ouId then just add the obs var data and timestamp to the row if (rowByOUId.get(ouId) != null) { @@ -385,11 +387,11 @@ private void addObsVarDataToRow( Map row, BrAPIObservation obs, boolean includeTimestamp, - BrAPIObservationVariable var, + Trait var, Program program) { String varName = Utilities.removeProgramKey(obs.getObservationVariableName(), program.getKey()); - if (var.getScale().getDataType().equals(BrAPITraitDataType.NUMERICAL) || - var.getScale().getDataType().equals(BrAPITraitDataType.DURATION)) { + if (var.getScale().getDataType().equals(DataType.NUMERICAL) || + var.getScale().getDataType().equals(DataType.DURATION)) { row.put(varName, Double.parseDouble(obs.getValue())); } else { row.put(varName, obs.getValue()); @@ -401,24 +403,25 @@ private void addObsVarDataToRow( } } - public List getDatasetObsVars(String datasetId, Program program) throws ApiException, DoesNotExistException { + public List getDatasetObsVars(String datasetId, Program program) throws ApiException, DoesNotExistException { List lists = listDAO.getListByTypeAndExternalRef( BrAPIListTypes.OBSERVATIONVARIABLES, program.getId(), String.format("%s/%s", referenceSource, ExternalReferenceSource.DATASET.getName()), UUID.fromString(datasetId)); if (lists == null || lists.isEmpty()) { - throw new DoesNotExistException("Dataset observation variables list not returned from BrAPI service"); + log.warn(String.format("Dataset %s observation variables list not returned from BrAPI service", datasetId)); + return new ArrayList<>(); } String listDbId = lists.get(0).getListDbId(); BrAPIListsSingleResponse list = listDAO.getListById(listDbId, program.getId()); - List obsVarNames = list.getResult().getData(); + List obsVarNames = list.getResult().getData().stream().map(var -> Utilities.removeProgramKey(var, program.getKey())).collect(Collectors.toList()); log.debug("Searching for dataset obsVars: " + obsVarNames); - List obsVars = obsVarDAO.getVariableByName(obsVarNames, program.getId()); + List obsVars = traitService.getByName(program.getId(), obsVarNames); log.debug(String.format("Found %d obsVars", obsVars.size())); // sort the obsVars to match the order stored in the dataset list - return fileMappingUtil.sortByField(obsVarNames, obsVars, BrAPIObservationVariable::getObservationVariableName); + return fileMappingUtil.sortByField(obsVarNames, obsVars, Trait::getObservationVariableName); } public BrAPITrial getExperiment(Program program, UUID experimentId) throws ApiException { @@ -500,14 +503,14 @@ private Map createExportRow( private void addObsVarColumns( List columns, - List obsVars, + List obsVars, boolean includeTimestamps, Program program) { - for (BrAPIObservationVariable var: obsVars) { + for (Trait var: obsVars) { Column obsVarColumn = new Column(); obsVarColumn.setDataType(Column.ColumnDataType.STRING); - if (var.getScale().getDataType().equals(BrAPITraitDataType.NUMERICAL) || - var.getScale().getDataType().equals(BrAPITraitDataType.DURATION)) { + if (var.getScale().getDataType().equals(DataType.NUMERICAL) || + var.getScale().getDataType().equals(DataType.DURATION)) { obsVarColumn.setDataType(Column.ColumnDataType.DOUBLE); } String varName = Utilities.removeProgramKey(var.getObservationVariableName(), program.getKey()); @@ -564,4 +567,13 @@ private void sortDefaultForExportRows(@NotNull List> exportR exportRows.sort(envComparator.thenComparing(expUnitIdComparator)); } + + public BrAPIStudy getEnvironment(Program program, UUID envId) throws ApiException { + List environments = studyDAO.getStudiesByEnvironmentIds(List.of(envId), program); + if (environments.isEmpty()) { + throw new RuntimeException("A study with given experiment id was not returned"); + } + + return environments.get(0); + } } diff --git a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationUnitDAO.java b/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationUnitDAO.java deleted file mode 100644 index 21fe0dbc2..000000000 --- a/src/main/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationUnitDAO.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * 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.brapps.importer.daos; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; -import io.micronaut.context.annotation.Property; -import org.brapi.client.v2.JSON; -import io.micronaut.http.server.exceptions.InternalServerException; -import org.brapi.client.v2.model.exceptions.ApiException; -import org.brapi.client.v2.modules.phenotype.ObservationUnitsApi; -import org.brapi.v2.model.BrAPIExternalReference; -import org.brapi.v2.model.germ.BrAPIGermplasm; -import org.brapi.v2.model.pheno.BrAPIObservationUnit; -import org.brapi.v2.model.pheno.request.BrAPIObservationUnitSearchRequest; -import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; -import org.breedinginsight.brapi.v2.services.BrAPIGermplasmService; -import org.brapi.v2.model.pheno.BrAPIObservationTreatment; -import org.brapi.v2.model.pheno.BrAPIObservationUnit; -import org.brapi.v2.model.pheno.request.BrAPIObservationUnitSearchRequest; -import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; -import org.breedinginsight.brapps.importer.model.ImportUpload; -import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; -import org.breedinginsight.daos.ProgramDAO; -import org.breedinginsight.model.Program; -import org.breedinginsight.services.ProgramService; -import org.breedinginsight.services.brapi.BrAPIEndpointProvider; -import org.breedinginsight.services.exceptions.DoesNotExistException; -import org.breedinginsight.utilities.BrAPIDAOUtil; -import org.breedinginsight.utilities.Utilities; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.validation.constraints.NotNull; -import java.lang.reflect.Type; -import java.util.*; - -@Singleton -public class BrAPIObservationUnitDAO { - private final ProgramDAO programDAO; - private final ImportDAO importDAO; - private final BrAPIDAOUtil brAPIDAOUtil; - private final BrAPIEndpointProvider brAPIEndpointProvider; - private final ProgramService programService; - private final BrAPIGermplasmService germplasmService; - - private final String referenceSource; - - private final Gson gson = new JSON().getGson(); - private final Type treatmentlistType = new TypeToken>(){}.getType(); - - @Inject - public BrAPIObservationUnitDAO(ProgramDAO programDAO, - ImportDAO importDAO, - BrAPIDAOUtil brAPIDAOUtil, - BrAPIEndpointProvider brAPIEndpointProvider, - BrAPIGermplasmService germplasmService, - ProgramService programService, - @Property(name = "brapi.server.reference-source") String referenceSource) { - this.programDAO = programDAO; - this.importDAO = importDAO; - this.brAPIDAOUtil = brAPIDAOUtil; - this.brAPIEndpointProvider = brAPIEndpointProvider; - this.referenceSource = referenceSource; - this.programService = programService; - this.germplasmService = germplasmService; - } - - public List getObservationUnitByName(List observationUnitNames, Program program) throws ApiException { - if(observationUnitNames.isEmpty()) { - return Collections.emptyList(); - } - - BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); - observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram().getProgramDbId())); - observationUnitSearchRequest.observationUnitNames(observationUnitNames); - - return searchObservationUnitsAndProcess(observationUnitSearchRequest, program.getId(), false); - } - - /** - * Create observation units, mutates brAPIObservationUnitList - */ - public List createBrAPIObservationUnits(List brAPIObservationUnitList, UUID programId, ImportUpload upload) throws ApiException { - ObservationUnitsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationUnitsApi.class); - preprocessObservationUnits(brAPIObservationUnitList); - List ous = brAPIDAOUtil.post(brAPIObservationUnitList, upload, api::observationunitsPost, importDAO::update); - processObservationUnits(programId, ous, false); - return ous; - } - - public BrAPIObservationUnit updateBrAPIObservationUnit(String ouDbId, BrAPIObservationUnit ou, UUID programId) { - ObservationUnitsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationUnitsApi.class); - BrAPIObservationUnit updatedOu = null; - try { - if (ou != null && !ouDbId.isBlank()) { - updatedOu = brAPIDAOUtil.put(ouDbId, ou, api::observationunitsObservationUnitDbIdPut); - } - return updatedOu; - } catch (Exception e) { - throw new InternalServerException("Unknown error has occurred: " + e.getMessage(), e); - } - } - - public List getObservationUnitsById(Collection observationUnitExternalIds, Program program) throws ApiException { - if(observationUnitExternalIds.isEmpty()) { - return Collections.emptyList(); - } - - BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); - observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram() - .getProgramDbId())); - observationUnitSearchRequest.externalReferenceIDs(new ArrayList<>(observationUnitExternalIds)); - observationUnitSearchRequest.externalReferenceSources(List.of(String.format("%s/%s", referenceSource, ExternalReferenceSource.OBSERVATION_UNITS.getName()))); - - return searchObservationUnitsAndProcess(observationUnitSearchRequest, program.getId(), false); - } - - public List getObservationUnitsForStudyDbId(@NotNull String studyDbId, Program program) throws ApiException { - BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); - observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram() - .getProgramDbId())); - observationUnitSearchRequest.studyDbIds(List.of(studyDbId)); - - return searchObservationUnitsAndProcess(observationUnitSearchRequest, program.getId(), false); - } - - public List getObservationUnitsForTrialDbId(@NotNull UUID programId, @NotNull String trialDbId) throws ApiException, DoesNotExistException { - return getObservationUnitsForTrialDbId(programId, trialDbId, false); - } - - public List getObservationUnitsForDataset(@NotNull String datasetId, @NotNull Program program) throws ApiException { - String datasetReferenceSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.DATASET); - BrAPIObservationUnitSearchRequest ouSearchRequest = new BrAPIObservationUnitSearchRequest(); - ouSearchRequest.programDbIds(List.of(program.getBrapiProgram().getProgramDbId())); - ouSearchRequest.externalReferenceSources(List.of(datasetReferenceSource)); - ouSearchRequest.externalReferenceIDs(List.of(datasetId)); - return searchObservationUnitsAndProcess(ouSearchRequest, program.getId(), true); - } - - public List getObservationUnitsForTrialDbId(@NotNull UUID programId, @NotNull String trialDbId, boolean withGID) throws ApiException, DoesNotExistException { - Program program = programService.getById(programId).orElseThrow(() -> new DoesNotExistException("Program id does not exist")); - - BrAPIObservationUnitSearchRequest observationUnitSearchRequest = new BrAPIObservationUnitSearchRequest(); - observationUnitSearchRequest.programDbIds(List.of(program.getBrapiProgram() - .getProgramDbId())); - observationUnitSearchRequest.trialDbIds(List.of(trialDbId)); - - return searchObservationUnitsAndProcess(observationUnitSearchRequest, programId, withGID); - } - - - /** - * Perform observation unit search and process returned observation units to handle any modifications to the data - * to be returned by bi-api - */ - private List searchObservationUnitsAndProcess(BrAPIObservationUnitSearchRequest request, UUID programId, boolean withGID) throws ApiException { - - ObservationUnitsApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), ObservationUnitsApi.class); - List brapiObservationUnits = brAPIDAOUtil.search(api::searchObservationunitsPost, - api::searchObservationunitsSearchResultsDbIdGet, - request); - - processObservationUnits(programId, brapiObservationUnits, withGID); - return brapiObservationUnits; - } - - private void processObservationUnits(UUID programId, List brapiObservationUnits, boolean withGID) throws ApiException { - - HashMap germplasmByDbId = new HashMap<>(); - if( withGID ){ - // Load germplasm for program into map. - // TODO: if we use redis search, that may be more efficient than loading all germplasm for the program. - this.germplasmService.getGermplasm(programId).forEach((germplasm -> germplasmByDbId.put(germplasm.getGermplasmDbId(), germplasm))); - } - - // if has treatments in additionalInfo, copy to treatments property - for (BrAPIObservationUnit ou : brapiObservationUnits) { - JsonObject additionalInfo = ou.getAdditionalInfo(); - if (additionalInfo != null) { - JsonElement treatmentsElement = additionalInfo.get(BrAPIAdditionalInfoFields.TREATMENTS); - if (treatmentsElement != null) { - List treatments = gson.fromJson(treatmentsElement, treatmentlistType); - ou.setTreatments(treatments); - } - if( withGID ){ - BrAPIGermplasm germplasm = germplasmByDbId.get(ou.getGermplasmDbId()); - ou.putAdditionalInfoItem(BrAPIAdditionalInfoFields.GID, germplasm.getAccessionNumber()); - } - } - } - } - - private void preprocessObservationUnits(List brapiObservationUnits) { - // add treatments to additional info - for (BrAPIObservationUnit obsUnit : brapiObservationUnits) { - List treatments = obsUnit.getTreatments(); - if (treatments != null) { - obsUnit.putAdditionalInfoItem(BrAPIAdditionalInfoFields.TREATMENTS, treatments); - } - } - } -} diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/ExperimentProcessor.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/ExperimentProcessor.java index 070b2214a..74065ffdf 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/ExperimentProcessor.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/ExperimentProcessor.java @@ -43,8 +43,7 @@ import org.breedinginsight.api.model.v1.response.ValidationError; import org.breedinginsight.api.model.v1.response.ValidationErrors; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; -import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; -import org.breedinginsight.brapps.importer.daos.*; +import org.breedinginsight.brapi.v2.dao.*; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; import org.breedinginsight.brapps.importer.model.imports.ChangeLogEntry; diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/GermplasmProcessor.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/GermplasmProcessor.java index f51f7c1fb..de5eb65ae 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/GermplasmProcessor.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/GermplasmProcessor.java @@ -33,7 +33,7 @@ import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; import org.breedinginsight.brapi.v2.services.BrAPIGermplasmService; -import org.breedinginsight.brapps.importer.daos.BrAPIListDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.base.Germplasm; import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/ObservationProcessor.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/ObservationProcessor.java index 693dda6e1..7fcefcd3d 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/ObservationProcessor.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/ObservationProcessor.java @@ -26,8 +26,8 @@ import org.brapi.v2.model.pheno.BrAPIObservation; import org.brapi.v2.model.pheno.BrAPIObservationUnit; import org.brapi.v2.model.pheno.BrAPIObservationVariable; -import org.breedinginsight.brapps.importer.daos.BrAPIObservationDAO; -import org.breedinginsight.brapps.importer.daos.BrAPIObservationVariableDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationVariableDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.base.Observation; import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/ObservationUnitProcessor.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/ObservationUnitProcessor.java index daad1eecd..6f1e6723a 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/ObservationUnitProcessor.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/ObservationUnitProcessor.java @@ -24,7 +24,7 @@ import org.brapi.v2.model.core.BrAPIStudy; import org.brapi.v2.model.germ.BrAPIGermplasm; import org.brapi.v2.model.pheno.BrAPIObservationUnit; -import org.breedinginsight.brapps.importer.daos.BrAPIObservationUnitDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationUnitDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.base.ObservationUnit; import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; @@ -34,6 +34,7 @@ import org.breedinginsight.brapps.importer.model.response.PendingImportObject; import org.breedinginsight.model.Program; import org.breedinginsight.model.User; +import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.services.exceptions.ValidatorException; import tech.tablesaw.api.Table; @@ -144,6 +145,8 @@ public void postBrapiData(Map mappedBrAPIImport, Program createdObservationUnits.addAll(brAPIObservationUnitDAO.createBrAPIObservationUnits(observationUnits, program.getId(), upload)); } catch (ApiException e) { throw new InternalServerException(e.toString(), e); + } catch (DoesNotExistException e) { + throw new InternalServerException(e.toString(), e); } // Update our records diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/SampleSubmissionProcessor.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/SampleSubmissionProcessor.java index d92ebb300..396bf8658 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/SampleSubmissionProcessor.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/SampleSubmissionProcessor.java @@ -31,9 +31,7 @@ import org.breedinginsight.api.model.v1.response.ValidationError; import org.breedinginsight.api.model.v1.response.ValidationErrors; import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; -import org.breedinginsight.brapps.importer.daos.BrAPIObservationUnitDAO; -import org.breedinginsight.brapps.importer.daos.BrAPIPlateDAO; -import org.breedinginsight.brapps.importer.daos.BrAPISampleDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationUnitDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; import org.breedinginsight.brapps.importer.model.imports.PendingImport; diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/StudyProcessor.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/StudyProcessor.java index 398eb2d88..e5f89e538 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/StudyProcessor.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/StudyProcessor.java @@ -23,7 +23,7 @@ import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.core.BrAPIStudy; import org.brapi.v2.model.core.BrAPITrial; -import org.breedinginsight.brapps.importer.daos.BrAPIStudyDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIStudyDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.base.Study; import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/TrialProcessor.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/TrialProcessor.java index bce850f51..1f838f9c6 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/TrialProcessor.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/TrialProcessor.java @@ -20,7 +20,7 @@ import io.micronaut.http.server.exceptions.InternalServerException; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.core.BrAPITrial; -import org.breedinginsight.brapps.importer.daos.BrAPITrialDAO; +import org.breedinginsight.brapi.v2.dao.BrAPITrialDAO; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.base.Trial; import org.breedinginsight.brapps.importer.model.imports.BrAPIImport; diff --git a/src/main/java/org/breedinginsight/daos/impl/AbstractDAO.java b/src/main/java/org/breedinginsight/daos/impl/AbstractDAO.java index 138cf73c4..50aee2608 100644 --- a/src/main/java/org/breedinginsight/daos/impl/AbstractDAO.java +++ b/src/main/java/org/breedinginsight/daos/impl/AbstractDAO.java @@ -161,7 +161,7 @@ public long count() throws DataAccessException { } @Override - public @Nullable P fetchOne(Field field, Z value) throws DataAccessException { + public P fetchOne(Field field, Z value) throws DataAccessException { return jooqDAO.fetchOne(field, value); } diff --git a/src/main/java/org/breedinginsight/daos/impl/TraitDAOImpl.java b/src/main/java/org/breedinginsight/daos/impl/TraitDAOImpl.java index 2dba86807..c61702622 100644 --- a/src/main/java/org/breedinginsight/daos/impl/TraitDAOImpl.java +++ b/src/main/java/org/breedinginsight/daos/impl/TraitDAOImpl.java @@ -614,32 +614,14 @@ private SelectOnConditionStep getTraitSql(BiUserTable createdByTableAlia @Override public List getTraitsByTraitName(UUID programId, List traits){ - String[] names = traits.stream() + List names = traits.stream() .filter(trait -> trait.getObservationVariableName() != null) .map(trait -> trait.getObservationVariableName().toLowerCase()) - .collect(Collectors.toList()).toArray(String[]::new); + .collect(Collectors.toList()); List traitResults = new ArrayList<>(); - if (names.length > 0){ - - Result records = dsl.select() - .from(TRAIT) - .join(PROGRAM_ONTOLOGY).on(TRAIT.PROGRAM_ONTOLOGY_ID.eq(PROGRAM_ONTOLOGY.ID)) - .join(PROGRAM).on(PROGRAM_ONTOLOGY.PROGRAM_ID.eq(PROGRAM.ID)) - .join(SCALE).on(TRAIT.SCALE_ID.eq(SCALE.ID)) - .join(METHOD).on(TRAIT.METHOD_ID.eq(METHOD.ID)) - .where(PROGRAM.ID.eq(programId)) - .and(lower(TRAIT.OBSERVATION_VARIABLE_NAME).in(names)) - .fetch(); - - for (Record record: records) { - Trait trait = Trait.parseSqlRecord(record); - Scale scale = Scale.parseSqlRecord(record); - Method method = Method.parseSqlRecord(record); - trait.setScale(scale); - trait.setMethod(method); - traitResults.add(trait); - } + if (!names.isEmpty()) { + traitResults = getTraitsFullByProgramId(programId).stream().filter(trait -> names.contains(trait.getObservationVariableName().toLowerCase())).collect(Collectors.toList()); } return traitResults; diff --git a/src/main/java/org/breedinginsight/model/Dataset.java b/src/main/java/org/breedinginsight/model/Dataset.java index 36bba2121..638c946f1 100644 --- a/src/main/java/org/breedinginsight/model/Dataset.java +++ b/src/main/java/org/breedinginsight/model/Dataset.java @@ -14,7 +14,7 @@ public class Dataset { public JsonObject additionalInfo; public List data; public List observationUnits; - public List observationVariables; + public List observationVariables; public enum DatasetStat { OBSERVATION_UNITS("observationUnits"), @@ -34,7 +34,7 @@ public Dataset( String experimentId, List data, List observationUnits, - List observationVariables) { + List observationVariables) { this.experimentId = experimentId; this.additionalInfo = new JsonObject(); this.data = data; diff --git a/src/main/java/org/breedinginsight/services/TraitService.java b/src/main/java/org/breedinginsight/services/TraitService.java index ff872791f..5f240e025 100644 --- a/src/main/java/org/breedinginsight/services/TraitService.java +++ b/src/main/java/org/breedinginsight/services/TraitService.java @@ -22,10 +22,19 @@ import io.micronaut.http.server.exceptions.HttpServerException; 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; import org.apache.commons.text.WordUtils; -import org.brapi.v2.model.pheno.BrAPIObservation; +import org.brapi.client.v2.model.exceptions.ApiException; +import org.brapi.v2.model.BrAPIOntologyReference; +import org.brapi.v2.model.core.BrAPIStudy; +import org.brapi.v2.model.core.BrAPITrial; +import org.brapi.v2.model.pheno.*; import org.breedinginsight.api.auth.AuthenticatedUser; import org.breedinginsight.api.model.v1.response.ValidationErrors; +import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; +import org.breedinginsight.brapi.v2.services.BrAPITrialService; +import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; import org.breedinginsight.dao.db.enums.DataType; import org.breedinginsight.dao.db.tables.pojos.MethodEntity; import org.breedinginsight.dao.db.tables.pojos.ProgramSharedOntologyEntity; @@ -37,6 +46,8 @@ import org.breedinginsight.services.exceptions.ValidatorException; import org.breedinginsight.services.validators.TraitValidatorError; import org.breedinginsight.services.validators.TraitValidatorService; +import org.breedinginsight.utilities.Utilities; +import org.jetbrains.annotations.NotNull; import org.jooq.DSLContext; import javax.inject.Inject; @@ -60,7 +71,6 @@ public class TraitService { private TraitValidatorService traitValidator; private DSLContext dsl; private TraitValidatorError traitValidatorError; - private final static String FAVORITES_TAG = "favorites"; @Inject @@ -475,4 +485,12 @@ public List getAllTraitTags(UUID programId) { tags.add(FAVORITES_TAG); return new ArrayList<>(tags); } + + public List getByName(UUID programId, List names) throws DoesNotExistException { + if (!programService.exists(programId)) { + throw new DoesNotExistException("Program does not exist"); + } + + return traitDAO.getTraitsByTraitName(programId, names.stream().map(name -> Trait.builder().observationVariableName(name).build()).collect(Collectors.toList())); + } } diff --git a/src/main/java/org/breedinginsight/utilities/response/mappers/StudyQueryMapper.java b/src/main/java/org/breedinginsight/utilities/response/mappers/StudyQueryMapper.java index f15aa620f..4a12acb4e 100644 --- a/src/main/java/org/breedinginsight/utilities/response/mappers/StudyQueryMapper.java +++ b/src/main/java/org/breedinginsight/utilities/response/mappers/StudyQueryMapper.java @@ -37,6 +37,13 @@ public class StudyQueryMapper extends AbstractQueryMapper { private final Map> fields; public StudyQueryMapper() { + /* + TODO + - observationVariableDbId + - active + - programDbId + - germplasmDbId + */ fields = Map.ofEntries( Map.entry("studyType", BrAPIStudy::getStudyType), Map.entry("locationDbId", BrAPIStudy::getLocationDbId), diff --git a/src/test/java/org/breedinginsight/brapi/v1/controller/BrapiObservationVariablesControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v1/controller/BrapiV1ObservationVariablesControllerIntegrationTest.java similarity index 99% rename from src/test/java/org/breedinginsight/brapi/v1/controller/BrapiObservationVariablesControllerIntegrationTest.java rename to src/test/java/org/breedinginsight/brapi/v1/controller/BrapiV1ObservationVariablesControllerIntegrationTest.java index 4da980a4e..300fcd928 100644 --- a/src/test/java/org/breedinginsight/brapi/v1/controller/BrapiObservationVariablesControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v1/controller/BrapiV1ObservationVariablesControllerIntegrationTest.java @@ -59,7 +59,7 @@ @MicronautTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class BrapiObservationVariablesControllerIntegrationTest extends BrAPITest { +public class BrapiV1ObservationVariablesControllerIntegrationTest extends BrAPITest { @Inject @Client(BrapiVersion.BRAPI_V1) diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationLevelsControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationLevelsControllerIntegrationTest.java new file mode 100644 index 000000000..0e57050ca --- /dev/null +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationLevelsControllerIntegrationTest.java @@ -0,0 +1,324 @@ +/* + * 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; + +import com.google.gson.*; +import io.kowalski.fannypack.FannyPack; +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.client.RxHttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.netty.cookies.NettyCookie; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.reactivex.Flowable; +import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.germ.BrAPIGermplasm; +import org.breedinginsight.BrAPITest; +import org.breedinginsight.TestUtils; +import org.breedinginsight.api.auth.AuthenticatedUser; +import org.breedinginsight.api.model.v1.request.ProgramRequest; +import org.breedinginsight.api.model.v1.request.SpeciesRequest; +import org.breedinginsight.api.v1.controller.TestTokenValidator; +import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; +import org.breedinginsight.brapps.importer.ImportTestUtils; +import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation; +import org.breedinginsight.dao.db.enums.DataType; +import org.breedinginsight.dao.db.tables.pojos.SpeciesEntity; +import org.breedinginsight.daos.SpeciesDAO; +import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.*; +import org.breedinginsight.services.OntologyService; +import org.breedinginsight.services.exceptions.ValidatorException; +import org.breedinginsight.services.parsers.experiment.ExperimentFileColumns; +import org.breedinginsight.services.writers.CSVWriter; +import org.jooq.DSLContext; +import org.junit.jupiter.api.*; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.*; + +import static io.micronaut.http.HttpRequest.GET; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BrAPIObservationLevelsControllerIntegrationTest extends BrAPITest { + + private Program program; + private String experimentId; + private List envIds = new ArrayList<>(); + private final List> rows = new ArrayList<>(); + private final List columns = ExperimentFileColumns.getOrderedColumns(); + private List traits; + + @Property(name = "brapi.server.reference-source") + private String BRAPI_REFERENCE_SOURCE; + @Inject + private DSLContext dsl; + @Inject + private UserDAO userDAO; + @Inject + private SpeciesDAO speciesDAO; + @Inject + private OntologyService ontologyService; + @Inject + private BrAPIGermplasmDAO germplasmDAO; + + @Inject + @Client("/${micronaut.bi.api.version}") + private RxHttpClient client; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer) + (json, type, context) -> OffsetDateTime.parse(json.getAsString())) + .create(); + + @BeforeAll + void setup() throws Exception { + FannyPack fp = FannyPack.fill("src/test/resources/sql/ImportControllerIntegrationTest.sql"); + ImportTestUtils importTestUtils = new ImportTestUtils(); + FannyPack securityFp = FannyPack.fill("src/test/resources/sql/ProgramSecuredAnnotationRuleIntegrationTest.sql"); + FannyPack brapiFp = FannyPack.fill("src/test/resources/sql/brapi/species.sql"); + + // Test User + User testUser = userDAO.getUserByOrcId(TestTokenValidator.TEST_USER_ORCID).orElseThrow(Exception::new); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Species + super.getBrapiDsl().execute(brapiFp.get("InsertSpecies")); + SpeciesEntity validSpecies = speciesDAO.findAll().get(0); + SpeciesRequest speciesRequest = SpeciesRequest.builder() + .commonName(validSpecies.getCommonName()) + .id(validSpecies.getId()) + .build(); + + // Test Program + ProgramRequest programRequest = ProgramRequest.builder() + .name("Test Program") + .abbreviation("Test") + .documentationUrl("localhost:8080") + .objective("To test things") + .species(speciesRequest) + .key("TEST") + .build(); + program = TestUtils.insertAndFetchTestProgram(gson, client, programRequest); + + dsl.execute(securityFp.get("InsertProgramRolesBreeder"), testUser.getId().toString(), program.getId()); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Get experiment import map + Flowable> call = client.exchange( + GET("/import/mappings?importName=ExperimentsTemplateMap") + .contentType(MediaType.APPLICATION_JSON) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class + ); + HttpResponse response = call.blockingFirst(); + String mappingId = JsonParser.parseString(Objects.requireNonNull(response.body())).getAsJsonObject() + .getAsJsonObject("result") + .getAsJsonArray("data") + .get(0).getAsJsonObject().get("id").getAsString(); + + // Add traits to program + traits = createTraits(2); + AuthenticatedUser user = new AuthenticatedUser(testUser.getName(), new ArrayList<>(), testUser.getId(), new ArrayList<>()); + try { + ontologyService.createTraits(program.getId(), traits, user, false); + } catch (ValidatorException e) { + System.err.println(e.getErrors()); + throw e; + } + + // Add germplasm to program + List germplasm = createGermplasm(1); + BrAPIExternalReference newReference = new BrAPIExternalReference(); + newReference.setReferenceSource(String.format("%s/programs", BRAPI_REFERENCE_SOURCE)); + newReference.setReferenceID(program.getId().toString()); + + germplasm.forEach(germ -> germ.getExternalReferences().add(newReference)); + + germplasmDAO.createBrAPIGermplasm(germplasm, program.getId(), null); + + // Make test experiment import + Map row1 = makeExpImportRow("Env1", "Plot"); + + //TODO once sub-entity support is added, update this to test for multiple levels + Map row2 = makeExpImportRow("Env2", "Plot"); + + // Add test observation data + for (Trait trait : traits) { + Random random = new Random(); + + // TODO: test for sending obs data as double. + // A float is returned from the backend instead of double. there is a separate card to fix this. + // Double val1 = Math.random(); + + Float val1 = random.nextFloat(); + row1.put(trait.getObservationVariableName(), val1); + } + + rows.add(row1); + rows.add(row2); + + // Import test experiment, environments, and any observations + JsonObject importResult = importTestUtils.uploadAndFetch( + writeDataToFile(rows, traits), + null, + true, + client, + program, + mappingId); + experimentId = importResult + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(0).getAsJsonObject() + .get("trial").getAsJsonObject() + .get("id").getAsString(); + // Add environmentIds. + envIds.add(getEnvId(importResult, 0)); + envIds.add(getEnvId(importResult, 1)); + } + + @Test + public void testGetObservationLevels() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observationlevels", program.getId())) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + JsonArray levels = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(1, levels.size()); + List levelNames = new ArrayList<>(); + for(var level : levels) { + levelNames.add(level.getAsJsonObject().get("levelName").getAsString().toLowerCase()); + } + assertTrue(levelNames.contains("plot")); + } + + private File writeDataToFile(List> data, List traits) throws IOException { + File file = File.createTempFile("test", ".csv"); + + if(traits != null) { + traits.forEach(trait -> columns.add( + Column.builder() + .value(trait.getObservationVariableName()) + .dataType(Column.ColumnDataType.STRING) + .build()) + ); + } + ByteArrayOutputStream byteArrayOutputStream = CSVWriter.writeToCSV(columns, data); + FileOutputStream fos = new FileOutputStream(file); + fos.write(byteArrayOutputStream.toByteArray()); + + return file; + } + + private String getEnvId(JsonObject result, int index) { + return result + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(index).getAsJsonObject() + .get("study").getAsJsonObject() + .get("brAPIObject").getAsJsonObject() + .get("externalReferences").getAsJsonArray() + .get(2).getAsJsonObject() + .get("referenceID").getAsString(); + } + + private List createGermplasm(int numToCreate) { + List germplasm = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String gid = ""+(i+1); + BrAPIGermplasm testGermplasm = new BrAPIGermplasm(); + testGermplasm.setGermplasmName(String.format("Germplasm %s [TEST-%s]", gid, gid)); + testGermplasm.setSeedSource("Wild"); + testGermplasm.setAccessionNumber(gid); + testGermplasm.setDefaultDisplayName(String.format("Germplasm %s", gid)); + JsonObject additionalInfo = new JsonObject(); + additionalInfo.addProperty("importEntryNumber", gid); + additionalInfo.addProperty("breedingMethod", "Allopolyploid"); + testGermplasm.setAdditionalInfo(additionalInfo); + List externalRef = new ArrayList<>(); + BrAPIExternalReference testReference = new BrAPIExternalReference(); + testReference.setReferenceSource(BRAPI_REFERENCE_SOURCE); + testReference.setReferenceID(UUID.randomUUID().toString()); + externalRef.add(testReference); + testGermplasm.setExternalReferences(externalRef); + germplasm.add(testGermplasm); + } + + return germplasm; + } + + private List createTraits(int numToCreate) { + List traits = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String varName = "tt_test_" + (i + 1); + traits.add(Trait.builder() + .observationVariableName(varName) + .fullName(varName) + .entity("Plant " + i) + .attribute("height " + i) + .traitDescription("test") + .programObservationLevel(ProgramObservationLevel.builder().name("Plot").build()) + .scale(Scale.builder() + .scaleName("test scale") + .dataType(DataType.NUMERICAL) + .validValueMin(0) + .validValueMax(100) + .build()) + .method(Method.builder() + .description("test method") + .methodClass("test method") + .build()) + .build()); + } + + return traits; + } + + private Map makeExpImportRow(String environment, String level) { + Map row = new HashMap<>(); + row.put(ExperimentObservation.Columns.GERMPLASM_GID, "1"); + row.put(ExperimentObservation.Columns.TEST_CHECK, "T"); + row.put(ExperimentObservation.Columns.EXP_TITLE, "Test Exp"); + row.put(ExperimentObservation.Columns.EXP_UNIT, level); + row.put(ExperimentObservation.Columns.EXP_TYPE, "Phenotyping"); + row.put(ExperimentObservation.Columns.ENV, environment); + row.put(ExperimentObservation.Columns.ENV_LOCATION, "Location A"); + row.put(ExperimentObservation.Columns.ENV_YEAR, "2023"); + row.put(ExperimentObservation.Columns.EXP_UNIT_ID, "a-1"); + row.put(ExperimentObservation.Columns.REP_NUM, "1"); + row.put(ExperimentObservation.Columns.BLOCK_NUM, "1"); + row.put(ExperimentObservation.Columns.ROW, "1"); + row.put(ExperimentObservation.Columns.COLUMN, "1"); + return row; + } +} diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationUnitControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationUnitControllerIntegrationTest.java new file mode 100644 index 000000000..a45dbb6f6 --- /dev/null +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationUnitControllerIntegrationTest.java @@ -0,0 +1,456 @@ +/* + * 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; + +import com.google.gson.*; +import io.kowalski.fannypack.FannyPack; +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.client.RxHttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.http.netty.cookies.NettyCookie; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.reactivex.Flowable; +import lombok.SneakyThrows; +import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.germ.BrAPIGermplasm; +import org.brapi.v2.model.pheno.BrAPIObservationUnit; +import org.brapi.v2.model.pheno.response.BrAPIObservationUnitSingleResponse; +import org.breedinginsight.BrAPITest; +import org.breedinginsight.TestUtils; +import org.breedinginsight.api.auth.AuthenticatedUser; +import org.breedinginsight.api.model.v1.request.ProgramRequest; +import org.breedinginsight.api.model.v1.request.SpeciesRequest; +import org.breedinginsight.api.v1.controller.TestTokenValidator; +import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; +import org.breedinginsight.brapps.importer.ImportTestUtils; +import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation; +import org.breedinginsight.dao.db.enums.DataType; +import org.breedinginsight.dao.db.tables.pojos.SpeciesEntity; +import org.breedinginsight.daos.SpeciesDAO; +import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.*; +import org.breedinginsight.services.OntologyService; +import org.breedinginsight.services.exceptions.ValidatorException; +import org.breedinginsight.services.parsers.experiment.ExperimentFileColumns; +import org.breedinginsight.services.writers.CSVWriter; +import org.jooq.DSLContext; +import org.junit.jupiter.api.*; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static io.micronaut.http.HttpRequest.*; +import static org.junit.jupiter.api.Assertions.*; + +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BrAPIObservationUnitControllerIntegrationTest extends BrAPITest { + + private Program program; + private String experimentId; + private List envIds = new ArrayList<>(); + private final List> rows = new ArrayList<>(); + private final List columns = ExperimentFileColumns.getOrderedColumns(); + private List traits; + + @Property(name = "brapi.server.reference-source") + private String BRAPI_REFERENCE_SOURCE; + @Inject + private DSLContext dsl; + @Inject + private UserDAO userDAO; + @Inject + private SpeciesDAO speciesDAO; + @Inject + private OntologyService ontologyService; + @Inject + private BrAPIGermplasmDAO germplasmDAO; + + @Inject + @Client("/${micronaut.bi.api.version}") + private RxHttpClient client; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer) + (json, type, context) -> OffsetDateTime.parse(json.getAsString())) + .create(); + + @BeforeAll + void setup() throws Exception { + FannyPack fp = FannyPack.fill("src/test/resources/sql/ImportControllerIntegrationTest.sql"); + ImportTestUtils importTestUtils = new ImportTestUtils(); + FannyPack securityFp = FannyPack.fill("src/test/resources/sql/ProgramSecuredAnnotationRuleIntegrationTest.sql"); + FannyPack brapiFp = FannyPack.fill("src/test/resources/sql/brapi/species.sql"); + + // Test User + User testUser = userDAO.getUserByOrcId(TestTokenValidator.TEST_USER_ORCID).orElseThrow(Exception::new); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Species + super.getBrapiDsl().execute(brapiFp.get("InsertSpecies")); + SpeciesEntity validSpecies = speciesDAO.findAll().get(0); + SpeciesRequest speciesRequest = SpeciesRequest.builder() + .commonName(validSpecies.getCommonName()) + .id(validSpecies.getId()) + .build(); + + // Test Program + ProgramRequest programRequest = ProgramRequest.builder() + .name("Test Program") + .abbreviation("Test") + .documentationUrl("localhost:8080") + .objective("To test things") + .species(speciesRequest) + .key("TEST") + .build(); + program = TestUtils.insertAndFetchTestProgram(gson, client, programRequest); + + dsl.execute(securityFp.get("InsertProgramRolesBreeder"), testUser.getId().toString(), program.getId()); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Get experiment import map + Flowable> call = client.exchange( + GET("/import/mappings?importName=ExperimentsTemplateMap") + .contentType(MediaType.APPLICATION_JSON) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class + ); + HttpResponse response = call.blockingFirst(); + String mappingId = JsonParser.parseString(Objects.requireNonNull(response.body())).getAsJsonObject() + .getAsJsonObject("result") + .getAsJsonArray("data") + .get(0).getAsJsonObject().get("id").getAsString(); + + // Add traits to program + traits = createTraits(2); + AuthenticatedUser user = new AuthenticatedUser(testUser.getName(), new ArrayList<>(), testUser.getId(), new ArrayList<>()); + try { + ontologyService.createTraits(program.getId(), traits, user, false); + } catch (ValidatorException e) { + System.err.println(e.getErrors()); + throw e; + } + + // Add germplasm to program + List germplasm = createGermplasm(1); + BrAPIExternalReference newReference = new BrAPIExternalReference(); + newReference.setReferenceSource(String.format("%s/programs", BRAPI_REFERENCE_SOURCE)); + newReference.setReferenceID(program.getId().toString()); + + germplasm.forEach(germ -> germ.getExternalReferences().add(newReference)); + + germplasmDAO.createBrAPIGermplasm(germplasm, program.getId(), null); + + // Make test experiment import + Map row1 = makeExpImportRow("Env1"); + Map row2 = makeExpImportRow("Env2"); + + // Add test observation data + for (Trait trait : traits) { + Random random = new Random(); + + // TODO: test for sending obs data as double. + // A float is returned from the backend instead of double. there is a separate card to fix this. + // Double val1 = Math.random(); + + Float val1 = random.nextFloat(); + row1.put(trait.getObservationVariableName(), val1); + } + + rows.add(row1); + rows.add(row2); + + // Import test experiment, environments, and any observations + JsonObject importResult = importTestUtils.uploadAndFetch( + writeDataToFile(rows, traits), + null, + true, + client, + program, + mappingId); + experimentId = importResult + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(0).getAsJsonObject() + .get("trial").getAsJsonObject() + .get("id").getAsString(); + // Add environmentIds. + envIds.add(getEnvId(importResult, 0)); + envIds.add(getEnvId(importResult, 1)); + } + + @Test + @SneakyThrows + public void testPostObsUnitNotFound() { + BrAPIObservationUnit ou = new BrAPIObservationUnit().observationUnitName("test"); + + Flowable> postCall = client.exchange( + POST(String.format("/programs/%s/brapi/v2/observationunits", + program.getId().toString()), List.of(ou)) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @SneakyThrows + public void testPutSingleOUNotFound() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observationunits?trialDbId=%s", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + + BrAPIObservationUnit ou = gson.fromJson(responseObj.get("result").getAsJsonObject().get("data").getAsJsonArray().get(0).getAsJsonObject(), BrAPIObservationUnit.class); + ou.setGermplasmName("updated"); + + Flowable> postCall = client.exchange( + PUT(String.format("/programs/%s/brapi/v2/observationunits/%s", + program.getId().toString(), ou.getObservationUnitDbId()), ou) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse errorResponse = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @SneakyThrows + public void testPutMultipleOUsNotFound() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observationunits?trialDbId=%s", program.getId(), experimentId)).bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + + BrAPIObservationUnit ou = gson.fromJson(responseObj.get("result").getAsJsonObject().get("data").getAsJsonArray().get(0).getAsJsonObject(), BrAPIObservationUnit.class); + ou.setGermplasmName("updated"); + + Flowable> postCall = client.exchange( + PUT(String.format("/programs/%s/brapi/v2/observationunits", + program.getId().toString()), Map.of(ou.getObservationUnitDbId(), ou)) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse errorResponse = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @SneakyThrows + public void testGetObsUnitTableNotFound() { + Flowable> getCall = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observationunits/table", + program.getId().toString())) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = getCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + public void testGetOUListByExpId() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observationunits?trialDbId=%s", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + + JsonArray ous = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(2, ous.size()); + List ouNames = new ArrayList<>(); + for(var study : ous) { + ouNames.add(study.getAsJsonObject().get("observationUnitName").getAsString()); + } + assertTrue(ouNames.contains("Env1 - a-1")); + assertTrue(ouNames.contains("Env2 - a-1")); + } + + @Test + public void testGetOUById() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observationunits?trialDbId=%s", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + JsonArray ous = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(2, ous.size()); + JsonObject ou = ous.get(0).getAsJsonObject(); + + Flowable> ouCall = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observationunits/%s", program.getId(), ou.get("observationUnitDbId").getAsString())) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse ouResponse = ouCall.blockingFirst(); + assertEquals(HttpStatus.OK, ouResponse.getStatus()); + + AtomicReference brAPIObservationUnitSingleResponse = new AtomicReference<>(); + Assertions.assertDoesNotThrow(() -> { + brAPIObservationUnitSingleResponse.set(gson.fromJson(ouResponse.body(), BrAPIObservationUnitSingleResponse.class)); + }); + + assertNotNull(brAPIObservationUnitSingleResponse.get()); + assertNotNull(brAPIObservationUnitSingleResponse.get().getResult()); + assertEquals(ou.get("observationUnitDbId").getAsString(), brAPIObservationUnitSingleResponse.get().getResult().getObservationUnitDbId()); + } + + private File writeDataToFile(List> data, List traits) throws IOException { + File file = File.createTempFile("test", ".csv"); + + if(traits != null) { + traits.forEach(trait -> columns.add( + Column.builder() + .value(trait.getObservationVariableName()) + .dataType(Column.ColumnDataType.STRING) + .build()) + ); + } + ByteArrayOutputStream byteArrayOutputStream = CSVWriter.writeToCSV(columns, data); + FileOutputStream fos = new FileOutputStream(file); + fos.write(byteArrayOutputStream.toByteArray()); + + return file; + } + + private String getEnvId(JsonObject result, int index) { + return result + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(index).getAsJsonObject() + .get("study").getAsJsonObject() + .get("brAPIObject").getAsJsonObject() + .get("externalReferences").getAsJsonArray() + .get(2).getAsJsonObject() + .get("referenceID").getAsString(); + } + + private List createGermplasm(int numToCreate) { + List germplasm = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String gid = ""+(i+1); + BrAPIGermplasm testGermplasm = new BrAPIGermplasm(); + testGermplasm.setGermplasmName(String.format("Germplasm %s [TEST-%s]", gid, gid)); + testGermplasm.setSeedSource("Wild"); + testGermplasm.setAccessionNumber(gid); + testGermplasm.setDefaultDisplayName(String.format("Germplasm %s", gid)); + JsonObject additionalInfo = new JsonObject(); + additionalInfo.addProperty("importEntryNumber", gid); + additionalInfo.addProperty("breedingMethod", "Allopolyploid"); + testGermplasm.setAdditionalInfo(additionalInfo); + List externalRef = new ArrayList<>(); + BrAPIExternalReference testReference = new BrAPIExternalReference(); + testReference.setReferenceSource(BRAPI_REFERENCE_SOURCE); + testReference.setReferenceID(UUID.randomUUID().toString()); + externalRef.add(testReference); + testGermplasm.setExternalReferences(externalRef); + germplasm.add(testGermplasm); + } + + return germplasm; + } + + private List createTraits(int numToCreate) { + List traits = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String varName = "tt_test_" + (i + 1); + traits.add(Trait.builder() + .observationVariableName(varName) + .fullName(varName) + .entity("Plant " + i) + .attribute("height " + i) + .traitDescription("test") + .programObservationLevel(ProgramObservationLevel.builder().name("Plot").build()) + .scale(Scale.builder() + .scaleName("test scale") + .dataType(DataType.NUMERICAL) + .validValueMin(0) + .validValueMax(100) + .build()) + .method(Method.builder() + .description("test method") + .methodClass("test method") + .build()) + .build()); + } + + return traits; + } + + private Map makeExpImportRow(String environment) { + Map row = new HashMap<>(); + row.put(ExperimentObservation.Columns.GERMPLASM_GID, "1"); + row.put(ExperimentObservation.Columns.TEST_CHECK, "T"); + row.put(ExperimentObservation.Columns.EXP_TITLE, "Test Exp"); + row.put(ExperimentObservation.Columns.EXP_UNIT, "Plot"); + row.put(ExperimentObservation.Columns.EXP_TYPE, "Phenotyping"); + row.put(ExperimentObservation.Columns.ENV, environment); + row.put(ExperimentObservation.Columns.ENV_LOCATION, "Location A"); + row.put(ExperimentObservation.Columns.ENV_YEAR, "2023"); + row.put(ExperimentObservation.Columns.EXP_UNIT_ID, environment+" - a-1"); + row.put(ExperimentObservation.Columns.REP_NUM, "1"); + row.put(ExperimentObservation.Columns.BLOCK_NUM, "1"); + row.put(ExperimentObservation.Columns.ROW, "1"); + row.put(ExperimentObservation.Columns.COLUMN, "1"); + return row; + } +} diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java new file mode 100644 index 000000000..4a0a78526 --- /dev/null +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIObservationsControllerIntegrationTest.java @@ -0,0 +1,412 @@ +/* + * 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; + +import com.google.gson.*; +import io.kowalski.fannypack.FannyPack; +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.client.RxHttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.http.netty.cookies.NettyCookie; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.reactivex.Flowable; +import lombok.SneakyThrows; +import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.germ.BrAPIGermplasm; +import org.brapi.v2.model.pheno.BrAPIObservation; +import org.breedinginsight.BrAPITest; +import org.breedinginsight.TestUtils; +import org.breedinginsight.api.auth.AuthenticatedUser; +import org.breedinginsight.api.model.v1.request.ProgramRequest; +import org.breedinginsight.api.model.v1.request.SpeciesRequest; +import org.breedinginsight.api.v1.controller.TestTokenValidator; +import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; +import org.breedinginsight.brapps.importer.ImportTestUtils; +import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation; +import org.breedinginsight.dao.db.enums.DataType; +import org.breedinginsight.dao.db.tables.pojos.SpeciesEntity; +import org.breedinginsight.daos.SpeciesDAO; +import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.*; +import org.breedinginsight.services.OntologyService; +import org.breedinginsight.services.exceptions.ValidatorException; +import org.breedinginsight.services.parsers.experiment.ExperimentFileColumns; +import org.breedinginsight.services.writers.CSVWriter; +import org.jooq.DSLContext; +import org.junit.jupiter.api.*; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.*; + +import static io.micronaut.http.HttpRequest.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BrAPIObservationsControllerIntegrationTest extends BrAPITest { + + private Program program; + private String experimentId; + private List envIds = new ArrayList<>(); + private final List> rows = new ArrayList<>(); + private final List columns = ExperimentFileColumns.getOrderedColumns(); + private List traits; + + @Property(name = "brapi.server.reference-source") + private String BRAPI_REFERENCE_SOURCE; + @Inject + private DSLContext dsl; + @Inject + private UserDAO userDAO; + @Inject + private SpeciesDAO speciesDAO; + @Inject + private OntologyService ontologyService; + @Inject + private BrAPIGermplasmDAO germplasmDAO; + + @Inject + @Client("/${micronaut.bi.api.version}") + private RxHttpClient client; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer) + (json, type, context) -> OffsetDateTime.parse(json.getAsString())) + .create(); + + @BeforeAll + void setup() throws Exception { + FannyPack fp = FannyPack.fill("src/test/resources/sql/ImportControllerIntegrationTest.sql"); + ImportTestUtils importTestUtils = new ImportTestUtils(); + FannyPack securityFp = FannyPack.fill("src/test/resources/sql/ProgramSecuredAnnotationRuleIntegrationTest.sql"); + FannyPack brapiFp = FannyPack.fill("src/test/resources/sql/brapi/species.sql"); + + // Test User + User testUser = userDAO.getUserByOrcId(TestTokenValidator.TEST_USER_ORCID).orElseThrow(Exception::new); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Species + super.getBrapiDsl().execute(brapiFp.get("InsertSpecies")); + SpeciesEntity validSpecies = speciesDAO.findAll().get(0); + SpeciesRequest speciesRequest = SpeciesRequest.builder() + .commonName(validSpecies.getCommonName()) + .id(validSpecies.getId()) + .build(); + + // Test Program + ProgramRequest programRequest = ProgramRequest.builder() + .name("Test Program") + .abbreviation("Test") + .documentationUrl("localhost:8080") + .objective("To test things") + .species(speciesRequest) + .key("TEST") + .build(); + program = TestUtils.insertAndFetchTestProgram(gson, client, programRequest); + + dsl.execute(securityFp.get("InsertProgramRolesBreeder"), testUser.getId().toString(), program.getId()); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Get experiment import map + Flowable> call = client.exchange( + GET("/import/mappings?importName=ExperimentsTemplateMap") + .contentType(MediaType.APPLICATION_JSON) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class + ); + HttpResponse response = call.blockingFirst(); + String mappingId = JsonParser.parseString(Objects.requireNonNull(response.body())).getAsJsonObject() + .getAsJsonObject("result") + .getAsJsonArray("data") + .get(0).getAsJsonObject().get("id").getAsString(); + + // Add traits to program + traits = createTraits(2); + AuthenticatedUser user = new AuthenticatedUser(testUser.getName(), new ArrayList<>(), testUser.getId(), new ArrayList<>()); + try { + ontologyService.createTraits(program.getId(), traits, user, false); + } catch (ValidatorException e) { + System.err.println(e.getErrors()); + throw e; + } + + // Add germplasm to program + List germplasm = createGermplasm(1); + BrAPIExternalReference newReference = new BrAPIExternalReference(); + newReference.setReferenceSource(String.format("%s/programs", BRAPI_REFERENCE_SOURCE)); + newReference.setReferenceID(program.getId().toString()); + + germplasm.forEach(germ -> germ.getExternalReferences().add(newReference)); + + germplasmDAO.createBrAPIGermplasm(germplasm, program.getId(), null); + + // Make test experiment import + Map row1 = makeExpImportRow("Env1"); + Map row2 = makeExpImportRow("Env2"); + + // Add test observation data + for (Trait trait : traits) { + Random random = new Random(); + + // TODO: test for sending obs data as double. + // A float is returned from the backend instead of double. there is a separate card to fix this. + // Double val1 = Math.random(); + + Float val1 = random.nextFloat(); + row1.put(trait.getObservationVariableName(), val1); + } + + rows.add(row1); + rows.add(row2); + + // Import test experiment, environments, and any observations + JsonObject importResult = importTestUtils.uploadAndFetch( + writeDataToFile(rows, traits), + null, + true, + client, + program, + mappingId); + experimentId = importResult + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(0).getAsJsonObject() + .get("trial").getAsJsonObject() + .get("id").getAsString(); + // Add environmentIds. + envIds.add(getEnvId(importResult, 0)); + envIds.add(getEnvId(importResult, 1)); + } + + @Test + @SneakyThrows + public void testPostObsNotFound() { + BrAPIObservation obs = new BrAPIObservation().observationUnitName("test"); + + Flowable> postCall = client.exchange( + POST(String.format("/programs/%s/brapi/v2/observations", + program.getId().toString()), List.of(obs)) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @SneakyThrows + public void testPutSingleObsNotFound() { + BrAPIObservation obs = new BrAPIObservation().observationDbId(UUID.randomUUID().toString()).germplasmName("updated"); + + Flowable> postCall = client.exchange( + PUT(String.format("/programs/%s/brapi/v2/observations/%s", + program.getId().toString(), obs.getObservationUnitDbId()), obs) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse errorResponse = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @SneakyThrows + public void testPutMultipleObsNotFound() { + BrAPIObservation obs = new BrAPIObservation().observationDbId(UUID.randomUUID().toString()).germplasmName("updated"); + + Flowable> postCall = client.exchange( + PUT(String.format("/programs/%s/brapi/v2/observations", + program.getId().toString()), Map.of(obs.getObservationDbId(), obs)) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse errorResponse = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @SneakyThrows + public void testGetObsTableNotFound() { + Flowable> getCall = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observations/table", + program.getId().toString())) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = getCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @Disabled("Disabled until fetching of observations is implemented") + public void testGetObsListByExpId() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observations?trialDbId=%s", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + } + + @Test + @Disabled("Disabled until fetching of observations is implemented") + public void testGetOUById() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observations?trialDbId=%s", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + JsonArray observations = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + JsonObject obs = observations.get(0).getAsJsonObject(); + + Flowable> ouCall = client.exchange( + GET(String.format("/programs/%s/brapi/v2/observations/%s", program.getId(), obs.get("observationDbId").getAsString())) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse ouResponse = ouCall.blockingFirst(); + assertEquals(HttpStatus.OK, ouResponse.getStatus()); + } + + private File writeDataToFile(List> data, List traits) throws IOException { + File file = File.createTempFile("test", ".csv"); + + if(traits != null) { + traits.forEach(trait -> columns.add( + Column.builder() + .value(trait.getObservationVariableName()) + .dataType(Column.ColumnDataType.STRING) + .build()) + ); + } + ByteArrayOutputStream byteArrayOutputStream = CSVWriter.writeToCSV(columns, data); + FileOutputStream fos = new FileOutputStream(file); + fos.write(byteArrayOutputStream.toByteArray()); + + return file; + } + + private String getEnvId(JsonObject result, int index) { + return result + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(index).getAsJsonObject() + .get("study").getAsJsonObject() + .get("brAPIObject").getAsJsonObject() + .get("externalReferences").getAsJsonArray() + .get(2).getAsJsonObject() + .get("referenceID").getAsString(); + } + + private List createGermplasm(int numToCreate) { + List germplasm = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String gid = ""+(i+1); + BrAPIGermplasm testGermplasm = new BrAPIGermplasm(); + testGermplasm.setGermplasmName(String.format("Germplasm %s [TEST-%s]", gid, gid)); + testGermplasm.setSeedSource("Wild"); + testGermplasm.setAccessionNumber(gid); + testGermplasm.setDefaultDisplayName(String.format("Germplasm %s", gid)); + JsonObject additionalInfo = new JsonObject(); + additionalInfo.addProperty("importEntryNumber", gid); + additionalInfo.addProperty("breedingMethod", "Allopolyploid"); + testGermplasm.setAdditionalInfo(additionalInfo); + List externalRef = new ArrayList<>(); + BrAPIExternalReference testReference = new BrAPIExternalReference(); + testReference.setReferenceSource(BRAPI_REFERENCE_SOURCE); + testReference.setReferenceID(UUID.randomUUID().toString()); + externalRef.add(testReference); + testGermplasm.setExternalReferences(externalRef); + germplasm.add(testGermplasm); + } + + return germplasm; + } + + private List createTraits(int numToCreate) { + List traits = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String varName = "tt_test_" + (i + 1); + traits.add(Trait.builder() + .observationVariableName(varName) + .fullName(varName) + .entity("Plant " + i) + .attribute("height " + i) + .traitDescription("test") + .programObservationLevel(ProgramObservationLevel.builder().name("Plot").build()) + .scale(Scale.builder() + .scaleName("test scale") + .dataType(DataType.NUMERICAL) + .validValueMin(0) + .validValueMax(100) + .build()) + .method(Method.builder() + .description("test method") + .methodClass("test method") + .build()) + .build()); + } + + return traits; + } + + private Map makeExpImportRow(String environment) { + Map row = new HashMap<>(); + row.put(ExperimentObservation.Columns.GERMPLASM_GID, "1"); + row.put(ExperimentObservation.Columns.TEST_CHECK, "T"); + row.put(ExperimentObservation.Columns.EXP_TITLE, "Test Exp"); + row.put(ExperimentObservation.Columns.EXP_UNIT, "Plot"); + row.put(ExperimentObservation.Columns.EXP_TYPE, "Phenotyping"); + row.put(ExperimentObservation.Columns.ENV, environment); + row.put(ExperimentObservation.Columns.ENV_LOCATION, "Location A"); + row.put(ExperimentObservation.Columns.ENV_YEAR, "2023"); + row.put(ExperimentObservation.Columns.EXP_UNIT_ID, environment+" - a-1"); + row.put(ExperimentObservation.Columns.REP_NUM, "1"); + row.put(ExperimentObservation.Columns.BLOCK_NUM, "1"); + row.put(ExperimentObservation.Columns.ROW, "1"); + row.put(ExperimentObservation.Columns.COLUMN, "1"); + return row; + } +} diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIStudiesControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIStudiesControllerIntegrationTest.java new file mode 100644 index 000000000..955c6e619 --- /dev/null +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIStudiesControllerIntegrationTest.java @@ -0,0 +1,410 @@ +/* + * 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; + +import com.google.gson.*; +import io.kowalski.fannypack.FannyPack; +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.client.RxHttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.http.netty.cookies.NettyCookie; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.reactivex.Flowable; +import lombok.SneakyThrows; +import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.core.BrAPIStudy; +import org.brapi.v2.model.core.response.BrAPIStudySingleResponse; +import org.brapi.v2.model.germ.BrAPIGermplasm; +import org.breedinginsight.BrAPITest; +import org.breedinginsight.TestUtils; +import org.breedinginsight.api.auth.AuthenticatedUser; +import org.breedinginsight.api.model.v1.request.ProgramRequest; +import org.breedinginsight.api.model.v1.request.SpeciesRequest; +import org.breedinginsight.api.v1.controller.TestTokenValidator; +import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; +import org.breedinginsight.brapps.importer.ImportTestUtils; +import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation; +import org.breedinginsight.dao.db.enums.DataType; +import org.breedinginsight.dao.db.tables.pojos.SpeciesEntity; +import org.breedinginsight.daos.SpeciesDAO; +import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.*; +import org.breedinginsight.services.OntologyService; +import org.breedinginsight.services.exceptions.ValidatorException; +import org.breedinginsight.services.parsers.experiment.ExperimentFileColumns; +import org.breedinginsight.services.writers.CSVWriter; +import org.jooq.DSLContext; +import org.junit.jupiter.api.*; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static io.micronaut.http.HttpRequest.*; +import static org.junit.jupiter.api.Assertions.*; + +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BrAPIStudiesControllerIntegrationTest extends BrAPITest { + private Program program; + private String experimentId; + private List envIds = new ArrayList<>(); + private final List> rows = new ArrayList<>(); + private final List columns = ExperimentFileColumns.getOrderedColumns(); + private List traits; + + @Property(name = "brapi.server.reference-source") + private String BRAPI_REFERENCE_SOURCE; + @Inject + private DSLContext dsl; + @Inject + private UserDAO userDAO; + @Inject + private SpeciesDAO speciesDAO; + @Inject + private OntologyService ontologyService; + @Inject + private BrAPIGermplasmDAO germplasmDAO; + + @Inject + @Client("/${micronaut.bi.api.version}") + private RxHttpClient client; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer) + (json, type, context) -> OffsetDateTime.parse(json.getAsString())) + .create(); + + @BeforeAll + void setup() throws Exception { + FannyPack fp = FannyPack.fill("src/test/resources/sql/ImportControllerIntegrationTest.sql"); + ImportTestUtils importTestUtils = new ImportTestUtils(); + FannyPack securityFp = FannyPack.fill("src/test/resources/sql/ProgramSecuredAnnotationRuleIntegrationTest.sql"); + FannyPack brapiFp = FannyPack.fill("src/test/resources/sql/brapi/species.sql"); + + // Test User + User testUser = userDAO.getUserByOrcId(TestTokenValidator.TEST_USER_ORCID).orElseThrow(Exception::new); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Species + super.getBrapiDsl().execute(brapiFp.get("InsertSpecies")); + SpeciesEntity validSpecies = speciesDAO.findAll().get(0); + SpeciesRequest speciesRequest = SpeciesRequest.builder() + .commonName(validSpecies.getCommonName()) + .id(validSpecies.getId()) + .build(); + + // Test Program + ProgramRequest programRequest = ProgramRequest.builder() + .name("Test Program") + .abbreviation("Test") + .documentationUrl("localhost:8080") + .objective("To test things") + .species(speciesRequest) + .key("TEST") + .build(); + program = TestUtils.insertAndFetchTestProgram(gson, client, programRequest); + + dsl.execute(securityFp.get("InsertProgramRolesBreeder"), testUser.getId().toString(), program.getId()); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Get experiment import map + Flowable> call = client.exchange( + GET("/import/mappings?importName=ExperimentsTemplateMap") + .contentType(MediaType.APPLICATION_JSON) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class + ); + HttpResponse response = call.blockingFirst(); + String mappingId = JsonParser.parseString(Objects.requireNonNull(response.body())).getAsJsonObject() + .getAsJsonObject("result") + .getAsJsonArray("data") + .get(0).getAsJsonObject().get("id").getAsString(); + + // Add traits to program + traits = createTraits(2); + AuthenticatedUser user = new AuthenticatedUser(testUser.getName(), new ArrayList<>(), testUser.getId(), new ArrayList<>()); + try { + ontologyService.createTraits(program.getId(), traits, user, false); + } catch (ValidatorException e) { + System.err.println(e.getErrors()); + throw e; + } + + // Add germplasm to program + List germplasm = createGermplasm(1); + BrAPIExternalReference newReference = new BrAPIExternalReference(); + newReference.setReferenceSource(String.format("%s/programs", BRAPI_REFERENCE_SOURCE)); + newReference.setReferenceID(program.getId().toString()); + + germplasm.forEach(germ -> germ.getExternalReferences().add(newReference)); + + germplasmDAO.createBrAPIGermplasm(germplasm, program.getId(), null); + + // Make test experiment import + Map row1 = makeExpImportRow("Env1"); + Map row2 = makeExpImportRow("Env2"); + + // Add test observation data + for (Trait trait : traits) { + Random random = new Random(); + + // TODO: test for sending obs data as double. + // A float is returned from the backend instead of double. there is a separate card to fix this. + // Double val1 = Math.random(); + + Float val1 = random.nextFloat(); + row1.put(trait.getObservationVariableName(), val1); + } + + rows.add(row1); + rows.add(row2); + + // Import test experiment, environments, and any observations + JsonObject importResult = importTestUtils.uploadAndFetch( + writeDataToFile(rows, traits), + null, + true, + client, + program, + mappingId); + experimentId = importResult + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(0).getAsJsonObject() + .get("trial").getAsJsonObject() + .get("id").getAsString(); + // Add environmentIds. + envIds.add(getEnvId(importResult, 0)); + envIds.add(getEnvId(importResult, 1)); + } + + @Test + @SneakyThrows + public void testPostGetStudiesNotFound() { + BrAPIStudy study = new BrAPIStudy().studyName("test study") + .studyCode("123") + .studyType("Phenotyping Trial") + .studyDescription("Test study description") + .active(true) + .startDate(OffsetDateTime.of(2021, 1, 5, 0, 0, 0, 0, ZoneOffset.UTC)) + .endDate(OffsetDateTime.of(2021, 2, 5, 0, 0, 0, 0, ZoneOffset.UTC)); + + Flowable> postCall = client.exchange( + POST(String.format("/programs/%s/brapi/v2/studies", + program.getId().toString()), List.of(study)) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @SneakyThrows + public void testPutGetStudiesNotFound() { + BrAPIStudy study = new BrAPIStudy().studyName("test study") + .studyCode("123") + .studyType("Phenotyping Trial") + .studyDescription("Test study description") + .active(true) + .startDate(OffsetDateTime.of(2021, 1, 5, 0, 0, 0, 0, ZoneOffset.UTC)) + .endDate(OffsetDateTime.of(2021, 2, 5, 0, 0, 0, 0, ZoneOffset.UTC)); + + Flowable> putCall = client.exchange( + PUT(String.format("/programs/%s/brapi/v2/studies/abc", + program.getId().toString()), List.of(study)) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = putCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + public void testGetStudiesListByExpId() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/studies?trialDbId=%s", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + JsonArray studies = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(2, studies.size()); + List studyNames = new ArrayList<>(); + for(var study : studies) { + studyNames.add(study.getAsJsonObject().get("studyName").getAsString()); + } + assertTrue(studyNames.contains("Env1")); + assertTrue(studyNames.contains("Env2")); + } + + @Test + public void testGetStudyById() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/studies?trialDbId=%s", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + JsonArray studies = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(2, studies.size()); + JsonObject study = studies.get(0).getAsJsonObject(); + + Flowable> studyCall = client.exchange( + GET(String.format("/programs/%s/brapi/v2/studies/%s", program.getId(), study.get("studyDbId").getAsString())) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse studyResponse = studyCall.blockingFirst(); + assertEquals(HttpStatus.OK, studyResponse.getStatus()); + + AtomicReference brAPIStudySingleResponse = new AtomicReference<>(); + Assertions.assertDoesNotThrow(() -> { + brAPIStudySingleResponse.set(gson.fromJson(studyResponse.body(), BrAPIStudySingleResponse.class)); + }); + + assertNotNull(brAPIStudySingleResponse.get()); + assertNotNull(brAPIStudySingleResponse.get().getResult()); + assertEquals(study.get("studyDbId").getAsString(), brAPIStudySingleResponse.get().getResult().getStudyDbId()); + } + + private File writeDataToFile(List> data, List traits) throws IOException { + File file = File.createTempFile("test", ".csv"); + + if(traits != null) { + traits.forEach(trait -> columns.add( + Column.builder() + .value(trait.getObservationVariableName()) + .dataType(Column.ColumnDataType.STRING) + .build()) + ); + } + ByteArrayOutputStream byteArrayOutputStream = CSVWriter.writeToCSV(columns, data); + FileOutputStream fos = new FileOutputStream(file); + fos.write(byteArrayOutputStream.toByteArray()); + + return file; + } + + private String getEnvId(JsonObject result, int index) { + return result + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(index).getAsJsonObject() + .get("study").getAsJsonObject() + .get("brAPIObject").getAsJsonObject() + .get("externalReferences").getAsJsonArray() + .get(2).getAsJsonObject() + .get("referenceID").getAsString(); + } + + private List createGermplasm(int numToCreate) { + List germplasm = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String gid = ""+(i+1); + BrAPIGermplasm testGermplasm = new BrAPIGermplasm(); + testGermplasm.setGermplasmName(String.format("Germplasm %s [TEST-%s]", gid, gid)); + testGermplasm.setSeedSource("Wild"); + testGermplasm.setAccessionNumber(gid); + testGermplasm.setDefaultDisplayName(String.format("Germplasm %s", gid)); + JsonObject additionalInfo = new JsonObject(); + additionalInfo.addProperty("importEntryNumber", gid); + additionalInfo.addProperty("breedingMethod", "Allopolyploid"); + testGermplasm.setAdditionalInfo(additionalInfo); + List externalRef = new ArrayList<>(); + BrAPIExternalReference testReference = new BrAPIExternalReference(); + testReference.setReferenceSource(BRAPI_REFERENCE_SOURCE); + testReference.setReferenceID(UUID.randomUUID().toString()); + externalRef.add(testReference); + testGermplasm.setExternalReferences(externalRef); + germplasm.add(testGermplasm); + } + + return germplasm; + } + + private List createTraits(int numToCreate) { + List traits = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String varName = "tt_test_" + (i + 1); + traits.add(Trait.builder() + .observationVariableName(varName) + .fullName(varName) + .entity("Plant " + i) + .attribute("height " + i) + .traitDescription("test") + .programObservationLevel(ProgramObservationLevel.builder().name("Plot").build()) + .scale(Scale.builder() + .scaleName("test scale") + .dataType(DataType.NUMERICAL) + .validValueMin(0) + .validValueMax(100) + .build()) + .method(Method.builder() + .description("test method") + .methodClass("test method") + .build()) + .build()); + } + + return traits; + } + + private Map makeExpImportRow(String environment) { + Map row = new HashMap<>(); + row.put(ExperimentObservation.Columns.GERMPLASM_GID, "1"); + row.put(ExperimentObservation.Columns.TEST_CHECK, "T"); + row.put(ExperimentObservation.Columns.EXP_TITLE, "Test Exp"); + row.put(ExperimentObservation.Columns.EXP_UNIT, "Plot"); + row.put(ExperimentObservation.Columns.EXP_TYPE, "Phenotyping"); + row.put(ExperimentObservation.Columns.ENV, environment); + row.put(ExperimentObservation.Columns.ENV_LOCATION, "Location A"); + row.put(ExperimentObservation.Columns.ENV_YEAR, "2023"); + row.put(ExperimentObservation.Columns.EXP_UNIT_ID, "a-1"); + row.put(ExperimentObservation.Columns.REP_NUM, "1"); + row.put(ExperimentObservation.Columns.BLOCK_NUM, "1"); + row.put(ExperimentObservation.Columns.ROW, "1"); + row.put(ExperimentObservation.Columns.COLUMN, "1"); + return row; + } +} diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ControllerIntegrationTest.java index ebb3979d1..1e1cb922b 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ControllerIntegrationTest.java @@ -25,6 +25,7 @@ import io.micronaut.http.MediaType; import io.micronaut.http.client.RxHttpClient; import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import io.reactivex.Flowable; import lombok.SneakyThrows; @@ -32,12 +33,7 @@ import org.brapi.v2.model.BrAPIExternalReference; import org.brapi.v2.model.BrAPIPagination; import org.brapi.v2.model.core.BrAPIServerInfo; -import org.brapi.v2.model.core.BrAPIStudy; -import org.brapi.v2.model.core.response.BrAPIStudyListResponse; -import org.brapi.v2.model.core.response.BrAPIStudySingleResponse; import org.brapi.v2.model.pheno.*; -import org.brapi.v2.model.pheno.response.BrAPIObservationVariableListResponse; -import org.brapi.v2.model.pheno.response.BrAPIObservationVariableSingleResponse; import org.breedinginsight.BrAPITest; import org.breedinginsight.api.v1.controller.TestTokenValidator; import org.breedinginsight.dao.db.tables.daos.ProgramDao; @@ -49,12 +45,12 @@ import javax.inject.Inject; import java.time.OffsetDateTime; -import java.time.ZoneOffset; import java.util.Arrays; import java.util.Collections; import java.util.UUID; -import static io.micronaut.http.HttpRequest.*; +import static io.micronaut.http.HttpRequest.GET; +import static io.micronaut.http.HttpRequest.POST; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -131,7 +127,7 @@ public void setup() { @Test public void testRootServerInfo() { - Flowable> call = biClient.exchange(GET("/brapi/v2/serverinfo"), String.class); + Flowable> call = biClient.exchange(GET("/v1/brapi/v2/serverinfo"), String.class); HttpResponse response = call.blockingFirst(); assertEquals(HttpStatus.OK, response.getStatus()); @@ -145,12 +141,15 @@ public void testRootServerInfo() { assertEquals("Breeding Insight", serverInfo.getOrganizationName()); assertEquals("DeltaBreed", serverInfo.getServerName()); assertEquals("bidevteam@cornell.edu", serverInfo.getContactEmail()); - assertEquals("breedinginsight.org", serverInfo.getOrganizationURL()); + assertEquals("https://breedinginsight.org", serverInfo.getOrganizationURL()); + assertEquals("BrAPI endpoints are not implemented at the root of this domain. Please make BrAPI calls in the context of a program (ex: https://app.breedinginsight.net/v1/programs/{programId}/brapi/v2)", serverInfo.getServerDescription()); + assertEquals("Cornell University, Ithaca, NY, USA", serverInfo.getLocation()); + assertEquals("https://brapi.org/specification", serverInfo.getDocumentationURL()); } @Test @SneakyThrows - public void testPostGetVariablesProxy() { + public void testPostVariablesNotFound() { BrAPIObservationVariable variable = generateVariable(); Flowable> postCall = biClient.exchange( @@ -161,94 +160,15 @@ public void testPostGetVariablesProxy() { .bearerAuth("test-registered-user"), String.class ); - HttpResponse postResponse; - try { - postResponse = postCall.blockingFirst(); - } catch (Exception e) { - throw new Exception(e); - } - //check the POST call was successful - assertEquals(HttpStatus.OK, postResponse.getStatus()); - - BrAPIObservationVariable createdVariable = GSON.fromJson(postResponse.body(), BrAPIObservationVariableListResponse.class) - .getResult() - .getData() - .get(0); - - //and that a variable is returned - assertNotNull(createdVariable); - //and that the variable has been assigned an ID - assertNotNull(createdVariable.getObservationVariableDbId(), "observationVariableDbId is null"); - - Flowable> getCall = biClient.exchange( - GET(String.format("%s/programs/%s/brapi/v2/variables/%s", - biApiVersion, - validProgram.getId().toString(), - createdVariable.getObservationVariableDbId())) - .bearerAuth("test-registered-user"), String.class - ); - - HttpResponse getResponse; - try { - getResponse = getCall.blockingFirst(); - } catch (Exception e) { - throw new Exception(e); - } - assertEquals(HttpStatus.OK, getResponse.getStatus()); - assertNotNull(getResponse.body(), "Response body is empty"); - BrAPIObservationVariableSingleResponse brAPIObservationVariableSingleResponse = GSON.fromJson(getResponse.body(), BrAPIObservationVariableSingleResponse.class); - - BrAPIObservationVariable fetchedVariable = brAPIObservationVariableSingleResponse.getResult(); - assertNotNull(fetchedVariable, "Observation Variable was not found"); - assertEquals(createdVariable.getObservationVariableDbId(), fetchedVariable.getObservationVariableDbId()); - //make sure the original values sent in the POST were saved correctly - assertEquals(variable.getObservationVariableName(), fetchedVariable.getObservationVariableName()); - assertEquals(variable.getExternalReferences(), fetchedVariable.getExternalReferences()); - assertEquals(variable.getCommonCropName(), fetchedVariable.getCommonCropName()); - - assertNotNull(fetchedVariable.getTrait(), "Trait is null"); - assertEquals(createdVariable.getTrait().getTraitDbId(), fetchedVariable.getTrait().getTraitDbId()); - //make sure the original values sent in the POST were saved correctly - assertEquals(variable.getTrait().getTraitName(), fetchedVariable.getTrait().getTraitName()); - assertEquals(variable.getTrait().getTraitClass(), fetchedVariable.getTrait().getTraitClass()); - - - assertNotNull(fetchedVariable.getMethod(), "Method is null"); - assertEquals(createdVariable.getMethod().getMethodDbId(), fetchedVariable.getMethod().getMethodDbId()); - //make sure the original values sent in the POST were saved correctly - assertEquals(variable.getMethod().getMethodName(), fetchedVariable.getMethod().getMethodName()); - assertEquals(variable.getMethod().getMethodClass(), fetchedVariable.getMethod().getMethodClass()); - - assertNotNull(fetchedVariable.getScale(), "Scale is null"); - assertEquals(createdVariable.getScale().getScaleDbId(), fetchedVariable.getScale().getScaleDbId()); - //make sure the original values sent in the POST were saved correctly - assertEquals(variable.getScale().getScaleName(), fetchedVariable.getScale().getScaleName()); - - Flowable> getScaleCall = biClient.exchange( - GET(String.format("%s/programs/%s/brapi/v2/scales/%s", - biApiVersion, - validProgram.getId().toString(), - createdVariable.getScale().getScaleDbId())) - .bearerAuth("test-registered-user"), String.class - ); - - HttpResponse getScaleResponse; - try { - getScaleResponse = getScaleCall.blockingFirst(); - } catch (Exception e) { - throw new Exception(e); - } - assertEquals(HttpStatus.OK, getScaleResponse.getStatus()); - - BrAPIScale scaleResponse = GSON.fromJson(JsonParser.parseString(getScaleResponse.body()).getAsJsonObject().getAsJsonObject("result"), BrAPIScale.class); - - //TODO this is not being returned -// assertEquals(variable.getScale().getDataType(), scaleResponse.getDataType()); + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); } @Test @SneakyThrows - public void testPutVariablesProxy() { + public void testPutVariablesNotFound() { BrAPIObservationVariable variable = generateVariable(); Flowable> postCall = biClient.exchange( @@ -259,127 +179,13 @@ public void testPutVariablesProxy() { .bearerAuth("test-registered-user"), String.class ); - HttpResponse postResponse; - try { - postResponse = postCall.blockingFirst(); - } catch (Exception e) { - throw new Exception(e); - } - //check the POST call was successful - assertEquals(HttpStatus.OK, postResponse.getStatus()); - - BrAPIObservationVariable createdVariable = GSON.fromJson(postResponse.body(), BrAPIObservationVariableListResponse.class) - .getResult() - .getData() - .get(0); - - createdVariable.setObservationVariableName("Updated variable name"); - - - Flowable> putCall = biClient.exchange( - PUT(String.format("%s/programs/%s/brapi/v2/variables/%s", - biApiVersion, - validProgram.getId().toString(), createdVariable.getObservationVariableDbId()), variable) - .contentType(MediaType.APPLICATION_JSON) - .bearerAuth("test-registered-user"), String.class - ); - - HttpResponse putResponse; - try { - putResponse = putCall.blockingFirst(); - } catch (Exception e) { - throw new Exception(e); - } - //check the PUT call was successful - assertEquals(HttpStatus.OK, putResponse.getStatus()); - - Flowable> getCall = biClient.exchange( - GET(String.format("%s/programs/%s/brapi/v2/variables/%s", - biApiVersion, - validProgram.getId().toString(), - createdVariable.getObservationVariableDbId())) - .bearerAuth("test-registered-user"), String.class - ); - - HttpResponse getResponse; - try { - getResponse = getCall.blockingFirst(); - } catch (Exception e) { - throw new Exception(e); - } - assertEquals(HttpStatus.OK, getResponse.getStatus()); - BrAPIObservationVariableSingleResponse brAPIObservationVariableSingleResponse = GSON.fromJson(getResponse.body(), BrAPIObservationVariableSingleResponse.class); - - BrAPIObservationVariable fetchedVariable = brAPIObservationVariableSingleResponse.getResult(); - //make sure the updated value persisted - assertEquals(variable.getObservationVariableName(), fetchedVariable.getObservationVariableName()); + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); } - @Test - @SneakyThrows - public void testPostGetStudiesProxy() { - BrAPIStudy study = new BrAPIStudy().studyName("test study") - .studyCode("123") - .studyType("Phenotyping Trial") - .studyDescription("Test study description") - .active(true) - .startDate(OffsetDateTime.of(2021, 1, 5, 0, 0, 0, 0, ZoneOffset.UTC)) - .endDate(OffsetDateTime.of(2021, 2, 5, 0, 0, 0, 0, ZoneOffset.UTC)); - - Flowable> postCall = biClient.exchange( - POST(String.format("%s/programs/%s/brapi/v2/studies", - biApiVersion, - validProgram.getId().toString()), Arrays.asList(study)) - .contentType(MediaType.APPLICATION_JSON) - .bearerAuth("test-registered-user"), String.class - ); - - HttpResponse postResponse; - try { - postResponse = postCall.blockingFirst(); - } catch (Exception e) { - throw new Exception(e); - } - //check the POST call was successful - assertEquals(HttpStatus.OK, postResponse.getStatus()); - - BrAPIStudy createdStudy = GSON.fromJson(postResponse.body(), BrAPIStudyListResponse.class) - .getResult() - .getData() - .get(0); - - //and that a study is returned - assertNotNull(createdStudy); - //and that the study has been assigned an ID - assertNotNull(createdStudy.getStudyDbId(), "studyDbId is null"); - - Flowable> getCall = biClient.exchange( - GET(String.format("%s/programs/%s/brapi/v2/studies/%s", - biApiVersion, - validProgram.getId().toString(), - createdStudy.getStudyDbId())) - .bearerAuth("test-registered-user"), String.class - ); - HttpResponse getResponse; - try { - getResponse = getCall.blockingFirst(); - } catch (Exception e) { - throw new Exception(e); - } - assertEquals(HttpStatus.OK, getResponse.getStatus()); - assertNotNull(getResponse.body(), "Response body is empty"); - - BrAPIStudy fetchedStudy = GSON.fromJson(getResponse.body(), BrAPIStudySingleResponse.class).getResult(); - - assertEquals(study.getStudyName(), fetchedStudy.getStudyName()); - assertEquals(study.getStudyCode(), fetchedStudy.getStudyCode()); - assertEquals(study.getStudyType(), fetchedStudy.getStudyType()); - assertEquals(study.getStudyDescription(), fetchedStudy.getStudyDescription()); - assertEquals(study.isActive(), fetchedStudy.isActive()); - assertEquals(study.getStartDate(), fetchedStudy.getStartDate()); - assertEquals(study.getEndDate(), fetchedStudy.getEndDate()); - } private BrAPIObservationVariable generateVariable() { var random = UUID.randomUUID() diff --git a/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ObservationVariableControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ObservationVariableControllerIntegrationTest.java new file mode 100644 index 000000000..d54aaafc1 --- /dev/null +++ b/src/test/java/org/breedinginsight/brapi/v2/BrAPIV2ObservationVariableControllerIntegrationTest.java @@ -0,0 +1,419 @@ +/* + * 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; + +import com.google.gson.*; +import io.kowalski.fannypack.FannyPack; +import io.micronaut.context.annotation.Property; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.client.RxHttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.http.netty.cookies.NettyCookie; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.reactivex.Flowable; +import lombok.SneakyThrows; +import org.brapi.v2.model.BrAPIExternalReference; +import org.brapi.v2.model.germ.BrAPIGermplasm; +import org.brapi.v2.model.pheno.BrAPIObservationVariable; +import org.breedinginsight.BrAPITest; +import org.breedinginsight.TestUtils; +import org.breedinginsight.api.auth.AuthenticatedUser; +import org.breedinginsight.api.model.v1.request.ProgramRequest; +import org.breedinginsight.api.model.v1.request.SpeciesRequest; +import org.breedinginsight.api.v1.controller.TestTokenValidator; +import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; +import org.breedinginsight.brapps.importer.ImportTestUtils; +import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation; +import org.breedinginsight.dao.db.enums.DataType; +import org.breedinginsight.dao.db.tables.pojos.SpeciesEntity; +import org.breedinginsight.daos.SpeciesDAO; +import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.*; +import org.breedinginsight.services.OntologyService; +import org.breedinginsight.services.exceptions.ValidatorException; +import org.breedinginsight.services.parsers.experiment.ExperimentFileColumns; +import org.breedinginsight.services.writers.CSVWriter; +import org.jooq.DSLContext; +import org.junit.jupiter.api.*; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.*; + +import static io.micronaut.http.HttpRequest.*; +import static org.junit.jupiter.api.Assertions.*; + +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BrAPIV2ObservationVariableControllerIntegrationTest extends BrAPITest { + private Program program; + private String experimentId; + private List envIds = new ArrayList<>(); + private final List> rows = new ArrayList<>(); + private final List columns = ExperimentFileColumns.getOrderedColumns(); + private List traits; + + @Property(name = "brapi.server.reference-source") + private String BRAPI_REFERENCE_SOURCE; + @Inject + private DSLContext dsl; + @Inject + private UserDAO userDAO; + @Inject + private SpeciesDAO speciesDAO; + @Inject + private OntologyService ontologyService; + @Inject + private BrAPIGermplasmDAO germplasmDAO; + + @Inject + @Client("/${micronaut.bi.api.version}") + private RxHttpClient client; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer) + (json, type, context) -> OffsetDateTime.parse(json.getAsString())) + .create(); + + @BeforeAll + void setup() throws Exception { + FannyPack fp = FannyPack.fill("src/test/resources/sql/ImportControllerIntegrationTest.sql"); + ImportTestUtils importTestUtils = new ImportTestUtils(); + FannyPack securityFp = FannyPack.fill("src/test/resources/sql/ProgramSecuredAnnotationRuleIntegrationTest.sql"); + FannyPack brapiFp = FannyPack.fill("src/test/resources/sql/brapi/species.sql"); + + // Test User + User testUser = userDAO.getUserByOrcId(TestTokenValidator.TEST_USER_ORCID).orElseThrow(Exception::new); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Species + super.getBrapiDsl().execute(brapiFp.get("InsertSpecies")); + SpeciesEntity validSpecies = speciesDAO.findAll().get(0); + SpeciesRequest speciesRequest = SpeciesRequest.builder() + .commonName(validSpecies.getCommonName()) + .id(validSpecies.getId()) + .build(); + + // Test Program + ProgramRequest programRequest = ProgramRequest.builder() + .name("Test Program") + .abbreviation("Test") + .documentationUrl("localhost:8080") + .objective("To test things") + .species(speciesRequest) + .key("TEST") + .build(); + program = TestUtils.insertAndFetchTestProgram(gson, client, programRequest); + + dsl.execute(securityFp.get("InsertProgramRolesBreeder"), testUser.getId().toString(), program.getId()); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Get experiment import map + Flowable> call = client.exchange( + GET("/import/mappings?importName=ExperimentsTemplateMap") + .contentType(MediaType.APPLICATION_JSON) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), String.class + ); + HttpResponse response = call.blockingFirst(); + String mappingId = JsonParser.parseString(Objects.requireNonNull(response.body())).getAsJsonObject() + .getAsJsonObject("result") + .getAsJsonArray("data") + .get(0).getAsJsonObject().get("id").getAsString(); + + // Add traits to program + traits = createTraits(4); + AuthenticatedUser user = new AuthenticatedUser(testUser.getName(), new ArrayList<>(), testUser.getId(), new ArrayList<>()); + try { + ontologyService.createTraits(program.getId(), traits, user, false); + } catch (ValidatorException e) { + System.err.println(e.getErrors()); + throw e; + } + + // Add germplasm to program + List germplasm = createGermplasm(1); + BrAPIExternalReference newReference = new BrAPIExternalReference(); + newReference.setReferenceSource(String.format("%s/programs", BRAPI_REFERENCE_SOURCE)); + newReference.setReferenceID(program.getId().toString()); + + germplasm.forEach(germ -> germ.getExternalReferences().add(newReference)); + + germplasmDAO.createBrAPIGermplasm(germplasm, program.getId(), null); + + // Make test experiment import + Map row1 = makeExpImportRow("Env1"); + Map row2 = makeExpImportRow("Env2"); + + // Add test observation data + List expTraits = List.of(traits.get(0), traits.get(1)); + for (int i = 0; i < 2; i++) { + var trait = expTraits.get(i); + Random random = new Random(); + + // TODO: test for sending obs data as double. + // A float is returned from the backend instead of double. there is a separate card to fix this. + // Double val1 = Math.random(); + + Float val1 = random.nextFloat(); + row1.put(trait.getObservationVariableName(), val1); + } + + rows.add(row1); + rows.add(row2); + + // Import test experiment, environments, and any observations + JsonObject importResult = importTestUtils.uploadAndFetch( + writeDataToFile(rows, expTraits), + null, + true, + client, + program, + mappingId); + experimentId = importResult + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(0).getAsJsonObject() + .get("trial").getAsJsonObject() + .get("id").getAsString(); + // Add environmentIds. + envIds.add(getEnvId(importResult, 0)); + envIds.add(getEnvId(importResult, 1)); + } + + @Test + @SneakyThrows + public void testPostVariablesNotFound() { + BrAPIObservationVariable variable = new BrAPIObservationVariable().observationVariableName(traits.get(0).getObservationVariableName()+" post"); + + Flowable> postCall = client.exchange( + POST(String.format("/programs/%s/brapi/v2/variables", + program.getId().toString()), List.of(variable)) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = postCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + @SneakyThrows + public void testPutVariablesNotFound() { + BrAPIObservationVariable variable = new BrAPIObservationVariable().observationVariableName(traits.get(0).getObservationVariableName()+" put"); + + Flowable> putCall = client.exchange( + PUT(String.format("/programs/%s/brapi/v2/variables/%s", + program.getId().toString(), traits.get(0).getId()), List.of(variable)) + .contentType(MediaType.APPLICATION_JSON) + .bearerAuth("test-registered-user"), String.class + ); + + HttpClientResponseException e = Assertions.assertThrows(HttpClientResponseException.class, () -> { + HttpResponse response = putCall.blockingFirst(); + }); + assertEquals(HttpStatus.NOT_FOUND, e.getStatus()); + } + + @Test + public void testGetVariables() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/variables", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + JsonArray variables = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(4, variables.size()); + List variableNames = new ArrayList<>(); + for(var variable : variables) { + variableNames.add(variable.getAsJsonObject().get("observationVariableName").getAsString()); + } + assertTrue(variableNames.contains("tt_test_1")); + assertTrue(variableNames.contains("tt_test_2")); + assertTrue(variableNames.contains("tt_test_3")); + assertTrue(variableNames.contains("tt_test_4")); + } + + @Test + public void testGetVariablesByExpId() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/variables?trialDbId=%s", program.getId(), experimentId)) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject responseObj = gson.fromJson(response.body(), JsonObject.class); + JsonArray variables = responseObj.getAsJsonObject("result").getAsJsonArray("data"); + assertEquals(2, variables.size()); + List variableNames = new ArrayList<>(); + for(var variable : variables) { + variableNames.add(variable.getAsJsonObject().get("observationVariableName").getAsString()); + } + assertTrue(variableNames.contains("tt_test_1")); + assertTrue(variableNames.contains("tt_test_2")); + } + + @Test + public void testGetVariableById() { + Flowable> call = client.exchange( + GET(String.format("/programs/%s/brapi/v2/variables/%s", program.getId(), traits.get(0).getId())) + .bearerAuth("test-registered-user"), + String.class + ); + + HttpResponse response = call.blockingFirst(); + assertEquals(HttpStatus.OK, response.getStatus()); + + 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).getObservationVariableName(), variable.get("observationVariableName").getAsString()); + + JsonObject trait = variable.getAsJsonObject("trait"); + assertEquals(traits.get(0).getEntity(), trait.get("entity").getAsString()); + assertEquals(traits.get(0).getAttribute(), trait.get("attribute").getAsString()); + + JsonObject method = variable.getAsJsonObject("method"); + assertEquals(traits.get(0).getMethod().getMethodClass(), + method.get("methodClass").getAsString()); + assertEquals(traits.get(0).getMethod().getDescription(), method.get("description").getAsString()); + + JsonObject scale = variable.getAsJsonObject("scale"); + assertEquals(traits.get(0).getScale().getScaleName(), scale.get("scaleName").getAsString()); + assertEquals(traits.get(0).getScale().getDataType().getLiteral().toLowerCase(), scale.get("dataType").getAsString().toLowerCase()); + assertEquals(traits.get(0).getScale().getValidValueMin(), scale.getAsJsonObject("validValues").get("min").getAsInt()); + assertEquals(traits.get(0).getScale().getValidValueMax(), scale.getAsJsonObject("validValues").get("max").getAsInt()); + } + + private File writeDataToFile(List> data, List traits) throws IOException { + File file = File.createTempFile("test", ".csv"); + + if(traits != null) { + traits.forEach(trait -> columns.add( + Column.builder() + .value(trait.getObservationVariableName()) + .dataType(Column.ColumnDataType.STRING) + .build()) + ); + } + ByteArrayOutputStream byteArrayOutputStream = CSVWriter.writeToCSV(columns, data); + FileOutputStream fos = new FileOutputStream(file); + fos.write(byteArrayOutputStream.toByteArray()); + + return file; + } + + private String getEnvId(JsonObject result, int index) { + return result + .get("preview").getAsJsonObject() + .get("rows").getAsJsonArray() + .get(index).getAsJsonObject() + .get("study").getAsJsonObject() + .get("brAPIObject").getAsJsonObject() + .get("externalReferences").getAsJsonArray() + .get(2).getAsJsonObject() + .get("referenceID").getAsString(); + } + + private List createGermplasm(int numToCreate) { + List germplasm = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String gid = ""+(i+1); + BrAPIGermplasm testGermplasm = new BrAPIGermplasm(); + testGermplasm.setGermplasmName(String.format("Germplasm %s [TEST-%s]", gid, gid)); + testGermplasm.setSeedSource("Wild"); + testGermplasm.setAccessionNumber(gid); + testGermplasm.setDefaultDisplayName(String.format("Germplasm %s", gid)); + JsonObject additionalInfo = new JsonObject(); + additionalInfo.addProperty("importEntryNumber", gid); + additionalInfo.addProperty("breedingMethod", "Allopolyploid"); + testGermplasm.setAdditionalInfo(additionalInfo); + List externalRef = new ArrayList<>(); + BrAPIExternalReference testReference = new BrAPIExternalReference(); + testReference.setReferenceSource(BRAPI_REFERENCE_SOURCE); + testReference.setReferenceID(UUID.randomUUID().toString()); + externalRef.add(testReference); + testGermplasm.setExternalReferences(externalRef); + germplasm.add(testGermplasm); + } + + return germplasm; + } + + private List createTraits(int numToCreate) { + List traits = new ArrayList<>(); + for (int i = 0; i < numToCreate; i++) { + String varName = "tt_test_" + (i + 1); + traits.add(Trait.builder() + .observationVariableName(varName) + .fullName(varName) + .entity("Plant " + i) + .attribute("height " + i) + .traitDescription("test") + .programObservationLevel(ProgramObservationLevel.builder().name("Plot").build()) + .scale(Scale.builder() + .scaleName("test scale") + .dataType(DataType.NUMERICAL) + .validValueMin(0) + .validValueMax(100) + .build()) + .method(Method.builder() + .description("test method") + .methodClass("test method") + .build()) + .build()); + } + + return traits; + } + + private Map makeExpImportRow(String environment) { + Map row = new HashMap<>(); + row.put(ExperimentObservation.Columns.GERMPLASM_GID, "1"); + row.put(ExperimentObservation.Columns.TEST_CHECK, "T"); + row.put(ExperimentObservation.Columns.EXP_TITLE, "Test Exp"); + row.put(ExperimentObservation.Columns.EXP_UNIT, "Plot"); + row.put(ExperimentObservation.Columns.EXP_TYPE, "Phenotyping"); + row.put(ExperimentObservation.Columns.ENV, environment); + row.put(ExperimentObservation.Columns.ENV_LOCATION, "Location A"); + row.put(ExperimentObservation.Columns.ENV_YEAR, "2023"); + row.put(ExperimentObservation.Columns.EXP_UNIT_ID, "a-1"); + row.put(ExperimentObservation.Columns.REP_NUM, "1"); + row.put(ExperimentObservation.Columns.BLOCK_NUM, "1"); + row.put(ExperimentObservation.Columns.ROW, "1"); + row.put(ExperimentObservation.Columns.COLUMN, "1"); + return row; + } +} diff --git a/src/test/java/org/breedinginsight/brapi/v2/GermplasmControllerIntegrationTest.java b/src/test/java/org/breedinginsight/brapi/v2/GermplasmControllerIntegrationTest.java index 0c52536fc..4fdf0cce3 100644 --- a/src/test/java/org/breedinginsight/brapi/v2/GermplasmControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/brapi/v2/GermplasmControllerIntegrationTest.java @@ -18,19 +18,18 @@ import org.breedinginsight.api.model.v1.request.query.FilterRequest; import org.breedinginsight.api.model.v1.request.query.SearchRequest; import org.breedinginsight.api.v1.controller.TestTokenValidator; -import org.breedinginsight.utilities.response.mappers.GermplasmQueryMapper; +import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; import org.breedinginsight.brapi.v2.services.BrAPIGermplasmService; -import org.breedinginsight.brapps.importer.daos.BrAPIListDAO; import org.breedinginsight.dao.db.tables.pojos.BiUserEntity; import org.breedinginsight.daos.UserDAO; import org.breedinginsight.model.Program; import org.breedinginsight.model.Species; import org.breedinginsight.services.SpeciesService; +import org.breedinginsight.utilities.response.mappers.GermplasmQueryMapper; import org.jooq.DSLContext; import org.junit.jupiter.api.*; import javax.inject.Inject; - import java.io.File; import java.time.OffsetDateTime; import java.util.ArrayList; diff --git a/src/test/java/org/breedinginsight/brapps/importer/ExperimentFileImportTest.java b/src/test/java/org/breedinginsight/brapps/importer/ExperimentFileImportTest.java index 8db3b5bf4..b3bcb0da1 100644 --- a/src/test/java/org/breedinginsight/brapps/importer/ExperimentFileImportTest.java +++ b/src/test/java/org/breedinginsight/brapps/importer/ExperimentFileImportTest.java @@ -45,8 +45,7 @@ import org.breedinginsight.api.model.v1.request.ProgramRequest; import org.breedinginsight.api.model.v1.request.SpeciesRequest; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; -import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; -import org.breedinginsight.brapps.importer.daos.*; +import org.breedinginsight.brapi.v2.dao.*; import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation.Columns; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; import org.breedinginsight.dao.db.tables.pojos.BiUserEntity; diff --git a/src/test/java/org/breedinginsight/brapps/importer/SampleSubmissionFileImportTest.java b/src/test/java/org/breedinginsight/brapps/importer/SampleSubmissionFileImportTest.java index aa446cc55..8c5b308c3 100644 --- a/src/test/java/org/breedinginsight/brapps/importer/SampleSubmissionFileImportTest.java +++ b/src/test/java/org/breedinginsight/brapps/importer/SampleSubmissionFileImportTest.java @@ -43,7 +43,7 @@ import org.breedinginsight.api.model.v1.request.ProgramRequest; import org.breedinginsight.api.model.v1.request.SpeciesRequest; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; -import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; +import org.breedinginsight.brapi.v2.dao.*; import org.breedinginsight.brapps.importer.daos.*; import org.breedinginsight.brapps.importer.model.imports.experimentObservation.ExperimentObservation; import org.breedinginsight.brapps.importer.model.imports.sample.SampleSubmissionImport.Columns; diff --git a/src/test/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationUnitDAOTest.java b/src/test/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationUnitDAOTest.java index ce742dc7c..b7a2ba564 100644 --- a/src/test/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationUnitDAOTest.java +++ b/src/test/java/org/breedinginsight/brapps/importer/daos/BrAPIObservationUnitDAOTest.java @@ -14,6 +14,7 @@ import org.breedinginsight.api.model.v1.request.SpeciesRequest; import org.breedinginsight.api.v1.controller.TestTokenValidator; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; +import org.breedinginsight.brapi.v2.dao.BrAPIObservationUnitDAO; import org.breedinginsight.brapps.importer.model.ImportProgress; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.dao.db.tables.pojos.SpeciesEntity; diff --git a/src/test/java/org/breedinginsight/services/BrAPIGermplasmServiceUnitTest.java b/src/test/java/org/breedinginsight/services/BrAPIGermplasmServiceUnitTest.java index 49d55d9e9..afa00df4e 100644 --- a/src/test/java/org/breedinginsight/services/BrAPIGermplasmServiceUnitTest.java +++ b/src/test/java/org/breedinginsight/services/BrAPIGermplasmServiceUnitTest.java @@ -12,7 +12,7 @@ import org.breedinginsight.DatabaseTest; import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; import org.breedinginsight.brapi.v2.services.BrAPIGermplasmService; -import org.breedinginsight.brapps.importer.daos.BrAPIListDAO; +import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.model.exports.FileType; import org.breedinginsight.daos.ProgramDAO; diff --git a/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java b/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java index 56b3c099b..452a36268 100644 --- a/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java +++ b/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java @@ -42,11 +42,11 @@ import org.brapi.v2.model.pheno.response.BrAPIObservationUnitListResponse; import org.brapi.v2.model.pheno.response.BrAPIObservationUnitListResponseResult; import org.breedinginsight.DatabaseTest; -import org.breedinginsight.brapps.importer.daos.BrAPITrialDAO; +import org.breedinginsight.brapi.v2.dao.BrAPITrialDAO; import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.daos.ImportMappingDAO; -import org.breedinginsight.brapps.importer.daos.impl.BrAPITrialDAOImpl; -import org.breedinginsight.brapps.importer.daos.impl.ImportMappingDAOImpl; +import org.breedinginsight.brapi.v2.dao.impl.BrAPITrialDAOImpl; +import org.breedinginsight.brapi.v2.dao.impl.ImportMappingDAOImpl; import org.breedinginsight.brapps.importer.model.ImportProgress; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.mapping.ImportMapping; diff --git a/src/test/java/org/breedinginsight/services/validators/TraitValidatorIntegrationTest.java b/src/test/java/org/breedinginsight/services/validators/TraitValidatorIntegrationTest.java index d5c3dc7d6..3006bc7b5 100644 --- a/src/test/java/org/breedinginsight/services/validators/TraitValidatorIntegrationTest.java +++ b/src/test/java/org/breedinginsight/services/validators/TraitValidatorIntegrationTest.java @@ -17,20 +17,34 @@ package org.breedinginsight.services.validators; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; import io.kowalski.fannypack.FannyPack; +import io.micronaut.http.client.RxHttpClient; +import io.micronaut.http.client.annotation.Client; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import lombok.SneakyThrows; -import org.breedinginsight.DatabaseTest; +import org.breedinginsight.BrAPITest; +import org.breedinginsight.TestUtils; +import org.breedinginsight.api.auth.AuthenticatedUser; +import org.breedinginsight.api.model.v1.request.ProgramRequest; +import org.breedinginsight.api.model.v1.request.SpeciesRequest; +import org.breedinginsight.api.v1.controller.TestTokenValidator; +import org.breedinginsight.dao.db.enums.DataType; import org.breedinginsight.dao.db.tables.daos.ProgramDao; import org.breedinginsight.dao.db.tables.pojos.ProgramEntity; -import org.breedinginsight.model.Method; -import org.breedinginsight.model.ProgramObservationLevel; -import org.breedinginsight.model.Scale; -import org.breedinginsight.model.Trait; +import org.breedinginsight.dao.db.tables.pojos.SpeciesEntity; +import org.breedinginsight.daos.SpeciesDAO; +import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.*; +import org.breedinginsight.services.TraitService; import org.jooq.DSLContext; import org.junit.jupiter.api.*; import javax.inject.Inject; +import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -39,7 +53,7 @@ @MicronautTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class TraitValidatorIntegrationTest extends DatabaseTest { +public class TraitValidatorIntegrationTest extends BrAPITest { private ProgramEntity validProgram; @@ -49,29 +63,76 @@ public class TraitValidatorIntegrationTest extends DatabaseTest { private ProgramDao programDao; @Inject private TraitValidatorService traitValidator; + @Inject + private TraitService traitService; + @Inject + private UserDAO userDAO; + @Inject + private SpeciesDAO speciesDAO; + @Inject + @Client("/${micronaut.bi.api.version}") + private RxHttpClient client; + + private final Gson gson = new GsonBuilder().registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer) + (json, type, context) -> OffsetDateTime.parse(json.getAsString())) + .create(); @BeforeAll - public void setup() { + public void setup() throws Exception { // Insert our traits into the db var fp = FannyPack.fill("src/test/resources/sql/TraitControllerIntegrationTest.sql"); - - // Insert program - dsl.execute(fp.get("InsertProgram")); + FannyPack securityFp = FannyPack.fill("src/test/resources/sql/ProgramSecuredAnnotationRuleIntegrationTest.sql"); + FannyPack brapiFp = FannyPack.fill("src/test/resources/sql/brapi/species.sql"); + + // Test User + User testUser = userDAO.getUserByOrcId(TestTokenValidator.TEST_USER_ORCID).orElseThrow(Exception::new); + dsl.execute(securityFp.get("InsertSystemRoleAdmin"), testUser.getId().toString()); + + // Species + super.getBrapiDsl().execute(brapiFp.get("InsertSpecies")); + SpeciesEntity validSpecies = speciesDAO.findAll().get(0); + SpeciesRequest speciesRequest = SpeciesRequest.builder() + .commonName(validSpecies.getCommonName()) + .id(validSpecies.getId()) + .build(); + + // Test Program + ProgramRequest programRequest = ProgramRequest.builder() + .name("Test Program") + .abbreviation("test") + .documentationUrl("localhost:8080") + .objective("To test things") + .species(speciesRequest) + .key("TEST") + .build(); + validProgram = TestUtils.insertAndFetchTestProgram(gson, client, programRequest); + + dsl.execute(securityFp.get("InsertProgramRolesBreeder"), testUser.getId().toString(), validProgram.getId()); + AuthenticatedUser user = new AuthenticatedUser(testUser.getName(), new ArrayList<>(), testUser.getId(), new ArrayList<>()); // Insert program observation level dsl.execute(fp.get("InsertProgramObservationLevel")); - // Insert program ontology sql - dsl.execute(fp.get("InsertProgramOntology")); + Trait trait = Trait.builder() + .observationVariableName("Test Trait") + .fullName("Test Trait") + .traitDescription("test") + .entity("test") + .attribute("test") + .programObservationLevel(ProgramObservationLevel.builder().name("Plot").build()) + .method(Method.builder() + .description("test method") + .methodClass("test method") + .build()) + .scale(Scale.builder() + .scaleName("Test Scale") + .dataType(DataType.TEXT) + .build()) + .build(); // Insert Trait - dsl.execute(fp.get("InsertMethod")); - dsl.execute(fp.get("InsertScale")); - dsl.execute(fp.get("InsertTrait")); - - // Retrieve our new data - validProgram = programDao.findAll().get(0); + traitService.createTraits(validProgram.getId(), new ArrayList<>(List.of(trait)), user, false); } @Test