ExternalRequestApiServiceImpl.java
/*
* Copyright 2025 Global Crop Diversity Trust
*
* 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.genesys.server.api.v2.facade.impl;
import lombok.extern.slf4j.Slf4j;
import org.genesys.blocks.oauth.persistence.OAuthClientRepository;
import org.genesys.server.api.v2.facade.ExternalRequestApiService;
import org.genesys.server.api.v2.mapper.MapstructMapper;
import org.genesys.server.api.v2.model.impl.ExternalRequestDTO;
import org.genesys.server.api.v2.model.impl.ExternalRequestGenesysDTO;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.model.genesys.AccessionRef;
import org.genesys.server.model.impl.ExternalRequest;
import org.genesys.server.model.impl.ExternalRequestAccessionRef;
import org.genesys.server.model.impl.QExternalRequest;
import org.genesys.server.persistence.ExternalRequestAccessionRefRepository;
import org.genesys.server.persistence.ExternalRequestRepository;
import org.genesys.server.service.ArticleService;
import org.genesys.server.service.ArticleTranslationService;
import org.genesys.server.service.ContentService;
import org.genesys.server.service.EMailService;
import org.genesys.server.service.worker.AccessionRefMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.validation.Valid;
@Service
@Slf4j
@Validated
public class ExternalRequestApiServiceImpl implements ExternalRequestApiService {
private static final long DAYS_BEFORE_EXPIRATION = 7;
@Value("${frontend.url}")
private String frontendUrl;
@Autowired
private ExternalRequestRepository externalRequestRepository;
@Autowired
private ExternalRequestAccessionRefRepository requestAccessionRefRepository;
@Autowired
private MapstructMapper mapstructMapper;
@Autowired
private AccessionRefMatcher accessionRefMatcher;
@Autowired
private ArticleService articleService;
@Autowired
private ContentService contentService;
@Autowired
private EMailService emailService;
@Autowired
private OAuthClientRepository oAuthClientRepository;
@Override
@Transactional(rollbackFor = { ExternalRequestAccessionsException.class })
public UUID registerExternalRequest(@Valid ExternalRequestDTO request, String remoteAddress) throws ExternalRequestAccessionsException {
List<ExternalRequestAccessionRef> requestAcceRefs = mapstructMapper.map(request.getItems(), mapstructMapper::map);
try {
log.info("Rematching {} dataset refs", requestAcceRefs.size());
accessionRefMatcher.rematchAccessionRefs(requestAcceRefs, requestAccessionRefRepository);
} catch (Throwable e) {
log.info("Rematch failed with {}", e.getMessage(), e);
}
var cannotBeRequested = requestAcceRefs.stream().filter(ref -> {
var accession = ref.getAccession();
if (accession == null) return true; // No such accession in Genesys
if (Objects.equals(true, accession.isHistoric())) return true; // Accession is historical
if (Objects.equals(false, accession.getAvailable())) return true; // Accession is not available for distribution
if (Objects.equals(false, accession.getInstitute().isAllowMaterialRequests())) return true; // Institute does not enable requests via Genesys
return false;
}).map(mapstructMapper::map).collect(Collectors.toList());
if (cannotBeRequested.size() > 0) {
// Some of the material cannot be requested through Genesys
throw new ExternalRequestAccessionsException(cannotBeRequested);
}
// Register the request
var externalRequest = new ExternalRequest();
externalRequest.setRemoteAddress(remoteAddress);
externalRequest.setUuid(UUID.randomUUID());
var pii = request.getPii();
externalRequest.setEmail(pii.getEmail());
externalRequest.setPid(pii.getPid());
externalRequest.setName(pii.getName());
var savedRequest = externalRequestRepository.save(externalRequest);
requestAcceRefs.forEach(requestAcceRef -> requestAcceRef.setRequest(savedRequest));
var savedAcceRefs = requestAccessionRefRepository.saveAll(requestAcceRefs);
var client = oAuthClientRepository.getReferenceById(savedRequest.getCreatedBy());
final ArticleTranslationService.TranslatedArticle article = articleService.getGlobalArticle(ContentService.SMTP_FROM_EXTERNAL_MATERIAL_REQUEST, LocaleContextHolder.getLocale());
if (article != null) {
final Map<String, Object> root = new HashMap<>();
root.put("origin", client.getTitle());
root.put("frontendUrl", frontendUrl);
root.put("accessions", savedAcceRefs.stream().map(AccessionRef::getAccession).collect(Collectors.toList()));
root.put("requestKey", savedRequest.getUuid());
var body = article.getTranslation() != null ? article.getTranslation().getBody() : article.getEntity().getBody();
var title = article.getTranslation() != null ? article.getTranslation().getTitle() : article.getEntity().getTitle();
final String mailBody = contentService.processTemplate(body, root);
final String mailSubject = title + " [" + savedRequest.getUuid() + "]";
log.debug(">>>{}", mailBody);
emailService.sendMail(mailSubject, mailBody, savedRequest.getEmail());
}
return savedRequest.getUuid();
}
@Override
@Transactional(readOnly = true)
public boolean externalRequestIsAvailable(UUID key) throws NotFoundElement {
if (!externalRequestRepository.exists(
QExternalRequest.externalRequest.uuid.eq(key)
.and(QExternalRequest.externalRequest.createdDate.after(Instant.now().minus(DAYS_BEFORE_EXPIRATION, ChronoUnit.DAYS)))
.and(QExternalRequest.externalRequest.materialRequest().isNull()))) {
throw new NotFoundElement();
}
return true;
}
@Override
@Transactional(readOnly = true)
public ExternalRequestGenesysDTO getRequest(UUID key) throws NotFoundElement {
var request = externalRequestRepository.findByUuid(key);
if (request == null || request.getMaterialRequest() != null || request.getCreatedDate().isBefore(Instant.now().minus(DAYS_BEFORE_EXPIRATION, ChronoUnit.DAYS))) {
throw new NotFoundElement();
}
return mapstructMapper.mapGenesys(request);
}
}