GlisSMTAReportingManager.java
/*
* Copyright 2022 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.gringlobal.service.glis.impl;
import static org.gringlobal.model.community.CommunityCodeValues.ORDER_REQUEST_ACTION_REPORT_TO_ITPGRFA;
import java.io.ByteArrayOutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.genesys.glis.v1.api.ManagerApi;
import org.genesys.glis.v1.model.SMTAActorType;
import org.genesys.glis.v1.model.SMTADocumentLocation;
import org.genesys.glis.v1.model.SMTADocumentPDFRequest;
import org.genesys.glis.v1.model.SMTADocumentPDFRequestRecipient;
import org.genesys.glis.v1.model.SMTAMaterial;
import org.genesys.glis.v1.model.SMTAMaterialUnderDevelopment;
import org.genesys.glis.v1.model.SMTAReport;
import org.genesys.glis.v1.model.SMTAReportDocument;
import org.genesys.glis.v1.model.SMTAReportProvider;
import org.genesys.glis.v1.model.SMTAReportRecipient;
import org.genesys.glis.v1.model.SMTAReportResponseError;
import org.genesys.glis.v1.model.SMTASignatureType;
import org.gringlobal.api.exception.InvalidApiUsageException;
import org.gringlobal.api.exception.NotFoundElement;
import org.gringlobal.model.AbstractAction;
import org.gringlobal.model.Accession;
import org.gringlobal.model.Cooperator;
import org.gringlobal.model.OrderRequest;
import org.gringlobal.model.OrderRequestItem;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.service.AppSettingsService;
import org.gringlobal.service.OrderRequestActionService;
import org.gringlobal.service.filter.OrderRequestActionFilter;
import org.gringlobal.service.filter.OrderRequestFilter;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@Slf4j
public class GlisSMTAReportingManager {
private static final String APPSETTINGS_ITPGRFA_EASYSMTA = "ITPGRFA_EASYSMTA";
private static final String DOI_PREFIX = "doi:";
public static final String RESPONSE_SUCCESS = "OK";
public static final String RESPONSE_ERROR = "KO";
static final String SETTING_PID = "pid";
static final String SETTING_PASSWORD = "password";
static final String SETTING_USERNAME = "username";
static final String SETTING_INSTITUTE_NAME = "instituteName";
static final String SETTING_INSTITUTE_ADDRESS = "instituteAddress";
static final String SETTING_INSTITUTE_COUNTRY_CODE = "instituteCountryCode";
static final String SETTING_SMTA_RETRIEVAL_INFO = "smtaRetrievalInfo";
static final String SETTING_SMTA_MATERIAL_URL = "smtaDataUrl";
private final SimpleDateFormat requestFormat = new SimpleDateFormat("yyyy-MM-dd");
private final JAXBContext context;
@Autowired
private AppSettingsService appSettingsService;
@Autowired
private OrderRequestActionService orderRequestActionService;
@Autowired
@Qualifier("glisSMTAManagerApiFactory")
private FactoryBean<ManagerApi> managerApiFactory;
public GlisSMTAReportingManager() throws JAXBException {
context = JAXBContext.newInstance(SMTAReport.class, SMTADocumentPDFRequest.class);
}
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
@Transactional(readOnly = true)
public GlisSMTADocumentPDFResponse generateSMTA(OrderRequest orderRequest) throws Exception {
var managerApi = createGlisManager();
if (managerApi == null) {
throw new InvalidApiUsageException("Easy-SMTA client not available.");
}
if (log.isDebugEnabled() || log.isTraceEnabled()) {
managerApi.getApiClient().setDebugging(true);
}
String glisUsername = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_USERNAME).getValue();
String glisPassword = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PASSWORD).getValue();
SMTADocumentPDFRequest smtaRequest = createDocumentPDFRequest(orderRequest);
var marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
marshaller.marshal(smtaRequest, outputStream);
try {
var response = managerApi.downloadSMTADocument("extsys/getpdf", glisUsername, glisPassword, outputStream.toByteArray());
var result = response.getResult();
if ("KO".equals(result)) {
var errors = response.getErrors();
var errorMessages = errors == null ? null : errors.stream().map(SMTAReportResponseError::getMsg).collect(Collectors.toList());
return new GlisSMTADocumentPDFResponse(RESPONSE_ERROR, errorMessages);
} else {
return new GlisSMTADocumentPDFResponse(RESPONSE_SUCCESS, response.getPdf());
}
} catch (Throwable e) {
log.error("Error interacting with GLIS SMTA API {}: {}", e.getClass(), e.getMessage());
return new GlisSMTADocumentPDFResponse(RESPONSE_ERROR, List.of(e.getMessage()));
}
}
private SMTADocumentPDFRequest createDocumentPDFRequest(OrderRequest orderRequest) {
String glisInstitutePid = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PID).getValue();
String language = "en";
SMTADocumentPDFRequestRecipient recipient = new SMTADocumentPDFRequestRecipient();
{
// var recipientCooperator = (Cooperator) Hibernate.unproxy(orderRequest.getFinalRecipientCooperator());
var recipientCooperator = orderRequest.getFinalRecipientCooperator();
recipient.type(recipientCooperator.getLastName() == null ? SMTAActorType.OR : SMTAActorType.IN)
.pid(recipientCooperator.getItpgrfaPid())
.name(
recipientCooperator.getLastName() == null ? // is organization
recipientCooperator.getOrganization()
: // else is individual
Stream.of(recipientCooperator.getFirstName(), recipientCooperator.getLastName())
.filter(StringUtils::isNotBlank).collect(Collectors.joining(" "))
)
.address(
Stream.of(recipientCooperator.getAddressLine1(), recipientCooperator.getAddressLine2(), recipientCooperator.getAddressLine3(), recipientCooperator.getCity())
.filter(StringUtils::isNotBlank).collect(Collectors.joining("\n"))
)
.country(recipientCooperator.getGeography() != null ? recipientCooperator.getGeography().getCountryCode() : null)
;
String tag = recipientCooperator.getSysLang().getIso6393Tag();
switch (tag) {
case "SPA":
language = "es";
break;
case "FRA":
language = "fr";
break;
case "ARA":
language = "ar";
break;
case "RUS":
language = "ru";
break;
}
}
if (orderRequest.getAuthorizedOfficialCooperator() != null) {
var autorizedOfficial = orderRequest.getAuthorizedOfficialCooperator();
recipient
.type(SMTAActorType.OR)
.name(autorizedOfficial.getOrganization())
.address(
Stream.of(autorizedOfficial.getAddressLine1(), autorizedOfficial.getAddressLine2(), autorizedOfficial.getAddressLine3(), autorizedOfficial.getCity())
.filter(StringUtils::isNotBlank).collect(Collectors.joining("\n"))
)
.country(autorizedOfficial.getGeography() != null ? autorizedOfficial.getGeography().getCountryCode() : null)
// AO
.aoname(Stream.of(autorizedOfficial.getFirstName(), autorizedOfficial.getLastName()).filter(StringUtils::isNotBlank).collect(Collectors.joining(" ")))
.aoemail(autorizedOfficial.getEmail())
.aofax(autorizedOfficial.getFax())
.aophone(autorizedOfficial.getPrimaryPhone())
;
}
URI glisDataUrl = null;
try {
glisDataUrl = new URL(appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_SMTA_MATERIAL_URL).getValue()).toURI();
if (!new UrlValidator().isValid(glisDataUrl.toString())) {
glisDataUrl = null;
log.error("Application setting {}:{} contains an invalid URL", APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_SMTA_MATERIAL_URL);
throw new InvalidApiUsageException("Application setting " + APPSETTINGS_ITPGRFA_EASYSMTA + ":" + SETTING_SMTA_MATERIAL_URL + " contains an invalid URL");
}
} catch (NotFoundElement e) {
log.info("Application setting {}:{} not found", APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_SMTA_MATERIAL_URL);
} catch (MalformedURLException | URISyntaxException e) {
log.error("Application setting {}:{} contains an invalid URL", APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_SMTA_MATERIAL_URL);
throw new InvalidApiUsageException("Application setting " + APPSETTINGS_ITPGRFA_EASYSMTA + ":" + SETTING_SMTA_MATERIAL_URL + " contains an invalid URL");
}
return new SMTADocumentPDFRequest()
.symbol(orderRequest.getLocalNumber() != null ? orderRequest.getLocalNumber() : "GGCE-OR-" + orderRequest.getId())
.date(orderRequest.getCompletedDate() != null ? requestFormat.format(orderRequest.getCompletedDate()) : requestFormat.format(new Date())) // Use now if completed date is missing
.type(getSMTAType(orderRequest)) // orderRequest.getWebOrderRequest() != null ? SMTASignatureType.SW : SMTASignatureType.CW; // We're not considering web order request
.language(language)
.providerPID(glisInstitutePid)
.recipient(recipient)
.dataUrl(glisDataUrl)
.annex1(orderRequest.getOrderRequestItems().stream().map(this::reportXmlMaterial).distinct().collect(Collectors.toList()));
}
private SMTASignatureType getSMTAType(OrderRequest orderRequest) {
if (Objects.equals(CommunityCodeValues.MTA_TYPE_SMTA_SIGNED.value, orderRequest.getMtaType())) {
return SMTASignatureType.SI;
} else if (Objects.equals(CommunityCodeValues.MTA_TYPE_SMTA_SHRINK.value, orderRequest.getMtaType())) {
return SMTASignatureType.SW;
} else if (Objects.equals(CommunityCodeValues.MTA_TYPE_SMTA_CLICK.value, orderRequest.getMtaType())) {
return SMTASignatureType.CW;
} else {
return SMTASignatureType.SI;
}
}
public static class GlisSMTADocumentPDFResponse {
public String status;
public List<String> errors;
public String pdf;
public GlisSMTADocumentPDFResponse(String status, List<String> errors) {
this.status = status;
this.errors = errors;
}
public GlisSMTADocumentPDFResponse(String status, String pdf) {
this.status = status;
this.pdf = pdf;
}
}
@PreAuthorize("@ggceSec.actionAllowed('Request', 'ADMINISTRATION')")
@Transactional(readOnly = true)
public GlisSMTAReportResponse uploadOrderRequestReport(OrderRequest orderRequest) throws Exception {
var managerApi = createGlisManager();
if (managerApi == null) {
throw new InvalidApiUsageException("Easy-SMTA client not available.");
}
if (log.isDebugEnabled() || log.isTraceEnabled()) {
managerApi.getApiClient().setDebugging(true);
}
String glisUsername = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_USERNAME).getValue();
String glisPassword = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PASSWORD).getValue();
var smtaReportXml = createReport(orderRequest);
var marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
marshaller.marshal(smtaReportXml, outputStream);
try {
var response = managerApi.uploadReport("extsys/uploadxml", glisUsername, glisPassword, outputStream.toByteArray(), null);
var errors = response.getError();
if (CollectionUtils.isNotEmpty(errors)) {
return new GlisSMTAReportResponse(RESPONSE_ERROR, errors.stream().map(SMTAReportResponseError::getMsg).collect(Collectors.toList()));
} else {
completeSMTAReportAction(orderRequest);
return new GlisSMTAReportResponse(RESPONSE_SUCCESS, null);
}
} catch (Throwable e) {
log.error("Error interacting with GLIS SMTA API {}: {}", e.getClass(), e.getMessage());
return new GlisSMTAReportResponse(RESPONSE_ERROR, List.of(e.getMessage()));
}
}
private void completeSMTAReportAction(OrderRequest orderRequest) {
var filter = new OrderRequestActionFilter();
filter.actionNameCode(Set.of(ORDER_REQUEST_ACTION_REPORT_TO_ITPGRFA.value));
filter
.orderRequest((OrderRequestFilter) new OrderRequestFilter().id(Set.of(orderRequest.getId())))
.states(Set.of(AbstractAction.ActionState.INPROGRESS));
var actions = orderRequestActionService.listActions(filter, Pageable.unpaged()).getContent();
var actionRequest = OrderRequestActionService.OrderRequestActionRequest.builder()
.actionNameCode(ORDER_REQUEST_ACTION_REPORT_TO_ITPGRFA.value)
.id(Set.of(orderRequest.getId()))
.build();
if (actions.isEmpty()) {
orderRequestActionService.startAction(actionRequest);
}
orderRequestActionService.completeAction(actionRequest);
}
public static class GlisSMTAReportResponse {
public String status;
public List<String> errors;
public GlisSMTAReportResponse(String status, List<String> errors) {
this.status = status;
this.errors = errors;
}
}
private SMTAReport createReport(OrderRequest orderRequest) {
String glisInstitutePid = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PID).getValue();
String glisInstituteName = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_NAME).getValue();
String glisInstituteAddress = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_ADDRESS).getValue();
String glisInstituteCountry = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_COUNTRY_CODE).getValue();
String providerEmail = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, "instituteEmail").getValue();
SMTAReportProvider provider = new SMTAReportProvider()
.type(SMTAActorType.OR)
.pid(glisInstitutePid)
.name(glisInstituteName)
.address(glisInstituteAddress)
.country(glisInstituteCountry)
.email(providerEmail);
var recipientCooperator = (Cooperator) Hibernate.unproxy(orderRequest.getFinalRecipientCooperator());
SMTAReportRecipient recipient = new SMTAReportRecipient()
.type(recipientCooperator.getLastName() == null ? SMTAActorType.OR : SMTAActorType.IN)
.pid(recipientCooperator.getItpgrfaPid())
.name(
recipientCooperator.getLastName() == null ? // is organization
recipientCooperator.getOrganization()
: // else is individual
Stream.of(recipientCooperator.getFirstName(), recipientCooperator.getLastName())
.filter(StringUtils::isNotBlank).collect(Collectors.joining(" "))
)
.address(
Stream.of(recipientCooperator.getAddressLine1(), recipientCooperator.getAddressLine2(), recipientCooperator.getAddressLine3(), recipientCooperator.getCity())
.filter(StringUtils::isNotBlank).collect(Collectors.joining("\n"))
)
.country(recipientCooperator.getGeography() != null ? recipientCooperator.getGeography().getCountryCode() : null);
// TODO Figure out which document to attach
// String pdf = null;
// try {
// var attach = orderRequestAttachRepository.findOne(QOrderRequestAttach.orderRequestAttach.orderRequest.id.eq(orderRequest.getId())).orElse(null);
// if (attach != null) {
// var bytes = repositoryService.getFileBytes(attach.getRepositoryFile());
// if (bytes != null) {
// pdf = Base64.encodeBase64String(bytes);
// }
// }
// } catch (IOException e) {
// LOG.warn("Exception in getting order request attach", e);
// }
SMTAReportDocument document = new SMTAReportDocument()
.location(SMTADocumentLocation.P)
.retInfo(appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_SMTA_RETRIEVAL_INFO, String.class).orElse(null))
.pdf(null);
String language = "en";
String tag = recipientCooperator.getSysLang().getIso6393Tag();
switch (tag) {
case "SPA":
language = "es";
break;
case "FRA":
language = "fr";
break;
case "ARA":
language = "ar";
break;
case "RUS":
language = "ru";
break;
}
String shipName;
if (recipientCooperator.getLastName() == null) {
String org = recipientCooperator.getOrganization();
if (org == null) {
org = recipientCooperator.getOrganizationAbbrev();
}
shipName = org;
} else {
shipName = recipientCooperator.getFirstName() + " " + recipientCooperator.getLastName();
}
shipName = shipName != null ? shipName : "UNKNOWN";
return new SMTAReport()
.symbol(orderRequest.getLocalNumber() != null ? orderRequest.getLocalNumber() : "GGCE-OR-" + orderRequest.getId())
.date(orderRequest.getCompletedDate() != null ? requestFormat.format(orderRequest.getCompletedDate()) : null)
.type(getSMTAType(orderRequest)) // orderRequest.getWebOrderRequest() != null ? SMTASignatureType.SW : SMTASignatureType.CW; // We're not considering web order request
.language(language)
.shipName(shipName)
.provider(provider)
.recipient(recipient)
.annex1(orderRequest.getOrderRequestItems().stream().map(this::reportXmlMaterial).distinct().collect(Collectors.toList()))
.document(document);
}
private SMTAMaterial reportXmlMaterial(OrderRequestItem item) {
Accession accession = (Accession) Hibernate.unproxy(item.getInventory().getAccession());
var material = new SMTAMaterial()
.crop(accession.getTaxonomySpecies().reportCropName())
.sampleID(accession.getDoi() != null ? DOI_PREFIX + accession.getDoi() : accession.getAccessionNumber())
.pud(SMTAMaterialUnderDevelopment.N)
;
if (Objects.equals(accession.getMlsStatus(), CommunityCodeValues.ACCESSION_MLS_STATUS_MLS_DEVELOP.value)) {
// Material under development
material
.pud(SMTAMaterialUnderDevelopment.Y)
.ancestry(accession.getAccessionPedigree() != null ? accession.getAccessionPedigree().getDescription() : null);
}
return material;
}
private ManagerApi createGlisManager() throws Exception {
var managerApi = managerApiFactory.getObject();
if (managerApi == null) {
throw new InvalidApiUsageException("Easy-SMTA client not available.");
}
if (log.isDebugEnabled() || log.isTraceEnabled()) {
managerApi.getApiClient().setDebugging(true);
}
try {
String glisUsername = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_USERNAME).getValue();
appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PASSWORD).getValue();
String glisInstitutePid = appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_PID).getValue();
appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_NAME).getValue();
appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_ADDRESS).getValue();
appSettingsService.getSetting(APPSETTINGS_ITPGRFA_EASYSMTA, SETTING_INSTITUTE_COUNTRY_CODE).getValue();
if (StringUtils.isBlank(glisUsername) || StringUtils.isBlank(glisInstitutePid)) {
throw new InvalidApiUsageException("Missing credentials for Easy-SMTA");
}
return managerApi;
} catch (NotFoundElement e) {
throw new InvalidApiUsageException("Easy-SMTA configuration is incomplete", e);
}
}
}