Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public GermplasmController(BrAPIGermplasmService germplasmService, GermplasmQuer
this.brAPIEndpointProvider = brAPIEndpointProvider;
}

// TODO: expand to fully support BrAPI request body.
@Post("/programs/{programId}" + BrapiVersion.BRAPI_V2 + "/search/germplasm{?queryParams*}")
@Produces(MediaType.APPLICATION_JSON)
@ProgramSecured(roleGroups = {ProgramSecuredRoleGroup.ALL})
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/org/breedinginsight/brapi/v2/StudyController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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.http.server.exceptions.InternalServerException;
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.services.exceptions.DoesNotExistException;
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<Response<DataResponse<List<BrAPIStudy>>>> getStudy(
@PathVariable("programId") UUID programId,
@QueryValue @QueryValid(using = StudyQueryMapper.class) @Valid StudyQuery queryParams) {
try {
log.debug("fetching studies for program: " + programId);

List<BrAPIStudy> 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");
}
}
}
198 changes: 198 additions & 0 deletions src/main/java/org/breedinginsight/brapi/v2/dao/BrAPIStudyDAO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
* 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.JsonObject;
import io.micronaut.context.annotation.Context;
import io.micronaut.context.annotation.Property;
import io.micronaut.http.server.exceptions.InternalServerException;
import io.micronaut.scheduling.annotation.Scheduled;
import lombok.extern.slf4j.Slf4j;
import org.brapi.client.v2.model.exceptions.ApiException;
import org.brapi.client.v2.modules.core.StudiesApi;
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.services.ExternalReferenceSource;
import org.breedinginsight.daos.ProgramDAO;
import org.breedinginsight.daos.cache.ProgramCache;
import org.breedinginsight.daos.cache.ProgramCacheProvider;
import org.breedinginsight.model.Program;
import org.breedinginsight.services.brapi.BrAPIEndpointProvider;
import org.breedinginsight.services.exceptions.DoesNotExistException;
import org.breedinginsight.utilities.BrAPIDAOUtil;
import org.breedinginsight.utilities.Utilities;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Singleton
@Context
public class BrAPIStudyDAO {

private final ProgramDAO programDAO;
private final BrAPIDAOUtil brAPIDAOUtil;

@Property(name = "brapi.server.reference-source")
private String referenceSource;

@Property(name = "micronaut.bi.api.run-scheduled-tasks")
private boolean runScheduledTasks;

private final ProgramCache<BrAPIStudy> programStudyCache;

private final BrAPIEndpointProvider brAPIEndpointProvider;

@Inject
public BrAPIStudyDAO(ProgramDAO programDAO, BrAPIDAOUtil brAPIDAOUtil, ProgramCacheProvider programCacheProvider, BrAPIEndpointProvider brAPIEndpointProvider) {
this.programDAO = programDAO;
this.brAPIDAOUtil = brAPIDAOUtil;
this.programStudyCache = programCacheProvider.getProgramCache(this::fetchProgramStudy, BrAPIStudy.class);
this.brAPIEndpointProvider = brAPIEndpointProvider;
}

@Scheduled(initialDelay = "2s")
public void setup() {
if(!runScheduledTasks) {
return;
}
// Populate study cache for all programs on startup
log.debug("populating study cache");
List<Program> programs = programDAO.getActive();
if(programs != null) {
programStudyCache.populate(programs.stream().map(Program::getId).collect(Collectors.toList()));
}
}

/**
* Fetch the study for this program, and process it to remove storage specific values
* @param programId
* @return this program's study
* @throws ApiException
*/
public List<BrAPIStudy> getStudies(UUID programId) throws ApiException {
return new ArrayList<>(programStudyCache.get(programId).values());
}

/**
* Fetch formatted study for this program
* @param programId
* @return Map<Key = string representing study UUID, value = formatted BrAPIStudy>
* @throws ApiException
*/
private Map<String, BrAPIStudy> fetchProgramStudy(UUID programId) throws ApiException {
StudiesApi api = brAPIEndpointProvider.get(programDAO.getCoreClient(programId), StudiesApi.class);
// Get the program key
List<Program> programs = programDAO.get(programId);
if (programs.size() != 1) {
throw new InternalServerException("Program was not found for given key");
}
Program program = programs.get(0);

// Set query params and make call
BrAPIStudySearchRequest studySearch = new BrAPIStudySearchRequest();
studySearch.externalReferenceIDs(List.of(programId.toString()));
studySearch.externalReferenceSources(List.of(Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.PROGRAMS)));
return processStudyForDisplay(brAPIDAOUtil.search(
api::searchStudiesPost,
api::searchStudiesSearchResultsDbIdGet,
studySearch
), program.getKey());
}

/**
* Process study into a format for display
* @param programStudy
* @return Map<Key = string representing study UUID, value = formatted BrAPIStudy>
* @throws ApiException
*/
private Map<String,BrAPIStudy> processStudyForDisplay(List<BrAPIStudy> programStudy, String programKey) {
// Process the study
Map<String, BrAPIStudy> programStudyMap = new HashMap<>();
log.trace("processing germ for display: " + programStudy);
Map<String, BrAPIStudy> programStudyByFullName = new HashMap<>();
for (BrAPIStudy study: programStudy) {
programStudyByFullName.put(study.getStudyName(), study);
// Remove program key from studyName, trialName and locationName.
if (study.getStudyName() != null) {
// Study name is appended with program key and experiment sequence number, need to remove.
study.setStudyName(Utilities.removeProgramKeyAndUnknownAdditionalData(study.getStudyName(), programKey));
}
if (study.getTrialName() != null) {
study.setTrialName(Utilities.removeProgramKey(study.getTrialName(), programKey));
}
if (study.getLocationName() != null) {
study.setLocationName(Utilities.removeProgramKey(study.getLocationName(), programKey));
}
}

String refSource = Utilities.generateReferenceSource(referenceSource, ExternalReferenceSource.STUDIES);
// Add to map.
for (BrAPIStudy study: programStudy) {
JsonObject additionalInfo = study.getAdditionalInfo();
if(additionalInfo == null) {
additionalInfo = new JsonObject();
study.setAdditionalInfo(additionalInfo);
}

BrAPIExternalReference extRef = study.getExternalReferences().stream()
.filter(reference -> reference.getReferenceSource().equals(refSource))
.findFirst().orElseThrow(() -> new IllegalStateException("No BI external reference found"));
String studyId = extRef.getReferenceID();
programStudyMap.put(studyId, study);
}

return programStudyMap;
}

public BrAPIStudy getStudyByUUID(String studyId, UUID programId) throws ApiException, DoesNotExistException {
Map<String, BrAPIStudy> cache = programStudyCache.get(programId);
BrAPIStudy study = null;
if (cache != null) {
study = cache.get(studyId);
}
if (study == null) {
throw new DoesNotExistException("UUID for this study does not exist");
}
return study;
}

public Optional<BrAPIStudy> getStudyByDBID(String studyDbId, UUID programId) throws ApiException {
Map<String, BrAPIStudy> cache = programStudyCache.get(programId);
//key is UUID, want to filter by DBID
BrAPIStudy study = null;
if (cache != null) {
study = cache.values().stream().filter(x -> x.getStudyDbId().equals(studyDbId)).collect(Collectors.toList()).get(0);
}
return Optional.ofNullable(study);
}

public List<BrAPIStudy> getStudiesByDBID(Collection<String> studyDbIds, UUID programId) throws ApiException {
Map<String, BrAPIStudy> cache = programStudyCache.get(programId);
//key is UUID, want to filter by DBID
List<BrAPIStudy> studies = new ArrayList<>();
if (cache != null) {
studies = cache.values().stream().filter(x -> studyDbIds.contains(x.getStudyDbId())).collect(Collectors.toList());
}
return studies;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.breedinginsight.brapi.v2.model.request.query;

import io.micronaut.core.annotation.Introspected;
import lombok.Getter;
import org.breedinginsight.api.model.v1.request.query.FilterRequest;
import org.breedinginsight.api.model.v1.request.query.SearchRequest;
import org.breedinginsight.brapi.v1.model.request.query.BrapiQuery;
import org.jooq.tools.StringUtils;

import java.util.ArrayList;
import java.util.List;

@Getter
@Introspected
public class StudyQuery extends BrapiQuery {
private String studyType;
private String locationDbId;
private String studyCode;
private String studyPUI;
private String commonCropName;
private String trialDbId;
private String studyDbId;
private String studyName;

public SearchRequest constructSearchRequest() {
List<FilterRequest> filters = new ArrayList<>();
if (!StringUtils.isBlank(getStudyType())) {
filters.add(constructFilterRequest("studyType", getStudyType()));
}
if (!StringUtils.isBlank(getLocationDbId())) {
filters.add(constructFilterRequest("locationDbId", getLocationDbId()));
}
if (!StringUtils.isBlank(getStudyCode())) {
filters.add(constructFilterRequest("studyCode", getStudyCode()));
}
if (!StringUtils.isBlank(getStudyPUI())) {
filters.add(constructFilterRequest("studyPUI", getStudyPUI()));
}
if (!StringUtils.isBlank(getCommonCropName())) {
filters.add(constructFilterRequest("commonCropName", getCommonCropName()));
}
if (!StringUtils.isBlank(getTrialDbId())) {
filters.add(constructFilterRequest("trialDbId", getTrialDbId()));
}
if (!StringUtils.isBlank(getStudyDbId())) {
filters.add(constructFilterRequest("studyDbId", getStudyDbId()));
}
if (!StringUtils.isBlank(getStudyName())) {
filters.add(constructFilterRequest("studyName", getStudyName()));
}
return new SearchRequest(filters);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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.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.brapi.v2.dao.BrAPIStudyDAO;
import org.breedinginsight.services.exceptions.DoesNotExistException;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.List;
import java.util.UUID;

@Slf4j
@Singleton
public class BrAPIStudyService {

private final BrAPIStudyDAO studyDAO;

@Inject
public BrAPIStudyService(BrAPIStudyDAO studyDAO) {
this.studyDAO = studyDAO;
}

public List<BrAPIStudy> getStudies(UUID programId) throws ApiException {
try {
return studyDAO.getStudies(programId);
} catch (ApiException e) {
throw new InternalServerException(e.getMessage(), e);
}
}

public BrAPIStudy getStudyByUUID(UUID programId, String studyId) throws DoesNotExistException {
try {
return studyDAO.getStudyByUUID(studyId, programId);
} catch (ApiException e) {
throw new InternalServerException(e.getMessage(), e);
}
}

}
Loading