AccessionTriggers.java
/*
* Copyright 2021 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.triggers;
import java.time.Instant;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.genesys.blocks.auditlog.model.AuditAction;
import org.genesys.blocks.auditlog.model.AuditLog;
import org.genesys.blocks.auditlog.service.ClassPKService;
import org.genesys.blocks.model.ClassPK;
import org.gringlobal.model.Accession;
import org.gringlobal.model.AccessionAction;
import org.gringlobal.model.AccessionInvAnnotation;
import org.gringlobal.model.AccessionInvName;
import org.gringlobal.model.Inventory;
import org.gringlobal.model.NameGroup;
import org.gringlobal.model.QAccessionInvName;
import org.gringlobal.model.QInventory;
import org.gringlobal.model.TaxonomySpecies;
import org.gringlobal.model.community.CommunityCodeValues;
import org.gringlobal.persistence.AccessionInvNameRepository;
import org.gringlobal.persistence.AccessionRepository;
import org.gringlobal.persistence.InventoryRepository;
import org.gringlobal.persistence.NameGroupRepository;
import org.gringlobal.service.AccessionActionService;
import org.gringlobal.service.AccessionInvAnnotationService;
import org.gringlobal.service.AccessionInvNameService;
import org.gringlobal.service.AccessionService;
import org.gringlobal.service.InventoryService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component("accessionTriggers")
@Slf4j
public class AccessionTriggers implements InitializingBean {
@Autowired
private AccessionRepository accessionRepository;
@Autowired
private InventoryRepository inventoryRepository;
@Autowired
public InventoryService inventoryService;
@Autowired
private AccessionService accessionService;
@Autowired
private NameGroupRepository nameGroupRepository;
@Autowired
private AccessionInvNameService accessionInvNameService;
@Autowired
private AccessionInvNameRepository accessionInvNameRepository;
@Autowired
private AccessionActionService actionService;
@Autowired
private AccessionInvAnnotationService accessionInvAnnotationService;
@Autowired
private ClassPKService classPKService;
private ClassPK accessionClassPk;
@Override
public void afterPropertiesSet() throws Exception {
accessionClassPk = classPKService.getClassPk(Accession.class);
}
@AfterReturning(value = "(execution(* org.genesys.blocks.auditlog.persistence.AuditLogRepository.save(..)) || execution(* org.genesys.blocks.auditlog.persistence.AuditLogRepository.saveAndFlush(..))) && args(auditLog)")
public void afterSaveAuditLogs(JoinPoint joinPoint, AuditLog auditLog) throws Throwable {
log.trace("Checking 1 saved auditLog, cpk={}", this.accessionClassPk);
afterSaveAuditLogs(null, List.of(auditLog));
}
@AfterReturning(value = "execution(* org.genesys.blocks.auditlog.service.*.addAuditLogs(..))", returning = "auditLogs")
public void afterSaveAuditLogs(JoinPoint joinPoint, Collection<AuditLog> auditLogs) throws Throwable {
log.trace("Checking {} saved auditLogs, cpk={}", auditLogs.size(), this.accessionClassPk);
var changedAccessions = new HashMap<Long, HashMap<String, Pair<Object, Object>>>();
auditLogs.stream().filter(this::isAccessionChangeLog).forEach(auditLog -> {
var changes = changedAccessions.get(auditLog.getEntityId());
if (changes == null) {
changedAccessions.put(auditLog.getEntityId(), changes = new HashMap<String, Pair<Object, Object>>());
}
log.trace("{}#{} {} -> {}", auditLog.getEntityId(), auditLog.getPropertyName(), auditLog.getPreviousState(), auditLog.getNewState());
log.trace("{}#{} {} -> {}", auditLog.getEntityId(), auditLog.getPropertyName(), auditLog.getPreviousEntity(), auditLog.getNewEntity());
changes.put(auditLog.getPropertyName(), Pair.of(auditLog.getPreviousEntity(), auditLog.getNewEntity()));
});
changedAccessions.forEach((accessionId, changes) -> {
perhapsRecordChanges(accessionId, changes);
});
}
// Utility
private boolean isAccessionChangeLog(final AuditLog auditLog) {
return auditLog != null && auditLog.getAction() == AuditAction.UPDATE && auditLog.getClassPk().equals(this.accessionClassPk);
}
private void perhapsRecordChanges(Long accessionId, HashMap<String, Pair<Object, Object>> changes) {
var taxonmySpeciesChange = changes.get("taxonomySpecies");
if (taxonmySpeciesChange != null) {
recordAccessionSpeciesChangesIfNeeded(accessionId, (TaxonomySpecies) taxonmySpeciesChange.getKey(), (TaxonomySpecies) taxonmySpeciesChange.getValue());
}
var accessionNumberChange = changes.get("accessionNumber");
if (accessionNumberChange != null) {
// We need accessionNumberPart1
var accessionNumberPart1Change = changes.get("accessionNumberPart1");
if (accessionNumberPart1Change != null) {
recordAccessionNumberIfNeeded(accessionId, (String) accessionNumberChange.getKey(), (String) accessionNumberChange.getValue(), (String) accessionNumberPart1Change.getKey(), (String) accessionNumberPart1Change.getValue());
} else {
recordAccessionNumberIfNeeded(accessionId, (String) accessionNumberChange.getKey(), (String) accessionNumberChange.getValue(), null, null);
}
}
}
@Around(value = "(execution(* org.gringlobal.persistence.AccessionRepository.save(..)) || execution(* org.gringlobal.persistence.AccessionRepository.saveAndFlush(..))) && args(accession)")
public Object aroundAccessionSave(final ProceedingJoinPoint joinPoint, final Accession accession) throws Throwable {
// assign the accessionNumberPart2 if needed
assignAccessionNumberPart2IfNeeded(accession);
if (!accession.isNew()) {
// it's updating, no need to assure systemInventory
Accession updatedAccession = (Accession) joinPoint.proceed();
// rename system inventories after updating
renameInventories(updatedAccession);
return updatedAccession;
} else {
// inserting new accession entry
Accession createdAccession = (Accession) joinPoint.proceed();
log.info("Assure system inventory for {}", createdAccession);
inventoryService.assureSystemInventory(createdAccession);
recordAccessionNumber(createdAccession.getId(), createdAccession.getAccessionNumber(), createdAccession.getAccessionNumberPart1());
recordReceivedAccessionSpecies(createdAccession);
return createdAccession;
}
}
@Before(value = "execution(* org.gringlobal.persistence.AccessionRepository.delete(*)) && args(accession)")
public void beforeDeleteAccession(final JoinPoint joinPoint, final Accession accession) throws Throwable {
accessionService.deleteDefaultInventory(accession);
}
@Before(value = "execution(* org.gringlobal.persistence.AccessionRepository.deleteAll(Iterable)) && args(accessions)")
public void beforeDeleteAccessions(final JoinPoint joinPoint, final Collection<Accession> accessions) throws Throwable {
log.debug("Many accessions are being deleted! {}", accessions);
accessions.forEach(accessionService::deleteDefaultInventory);
}
private void assignAccessionNumberPart2IfNeeded(Accession accession) {
if (AccessionService.AUTO_GENERATE_VALUE.equals(accession.getAccessionNumberPart2())) {
// assign the next within accessionNumberPart1
long maxPart2 = accessionRepository.findMaxNumberPart2(accession.getAccessionNumberPart1());
log.debug("Using next numberPart2 {}+1 for accession {}", maxPart2, accession.getAccessionNumberPart1());
accession.setAccessionNumberPart2(1 + maxPart2);
}
}
/**
* Rename system inventories to match accession number parts
*
* @param accession the accession
*/
private void renameInventories(final Accession accession) {
if (accession.getInventories() != null) {
log.info("Renaming accession SYSTEM inventories for updated accession {}", accession.getId());
inventoryRepository.findAll(QInventory.inventory.accession().eq(accession).and(QInventory.inventory.formTypeCode.eq(Inventory.SYSTEM_INVENTORY_FTC)))
// rename
.forEach(inventory -> {
inventory.setInventoryNumberPart1(accession.getAccessionNumberPart1());
inventory.setInventoryNumberPart2(accession.getAccessionNumberPart2());
inventory.setInventoryNumberPart3(accession.getAccessionNumberPart3());
inventoryRepository.save(inventory);
});
}
}
private void recordAccessionNumberIfNeeded(long accessionId, String oldNumber, String newNumber, String oldNumberPart1, String newNumberPart1) {
log.trace("Recording name change aid={} {}/{} -> {}/{}", accessionId, oldNumberPart1, oldNumber, newNumberPart1, newNumber);
if (newNumber != null && !newNumber.equals(oldNumber)) {
log.debug("Recording name change aid={} {}/{} -> {}/{}", accessionId, oldNumberPart1, oldNumber, newNumberPart1, newNumber);
var accession = new Accession(accessionId);
var qAIN = QAccessionInvName.accessionInvName;
if (IterableUtils.isEmpty(accessionInvNameRepository.findAll(
// system inventory of accession
qAIN.inventory().formTypeCode.eq(Inventory.SYSTEM_INVENTORY_FTC).and(qAIN.inventory().accession().eq(accession))
// name type
.and(qAIN.categoryCode.eq(CommunityCodeValues.ACCESSION_NAME_TYPE_SITE.value))))) {
// previous accession number isn't yet registered, do it now
recordAccessionNumber(accessionId, oldNumber, oldNumberPart1);
}
// register changed accession number
recordAccessionNumber(accessionId, newNumber, newNumberPart1);
// register new accession action
var action = new AccessionAction();
action.setAccession(accession);
action.setIsWebVisible("N");
action.setActionNameCode(CommunityCodeValues.ACCESSION_ACTION_ACCNUMBERD.value);
action.setNote("Changed " + oldNumber + " to " + newNumber);
action.setStartedDate(Instant.now());
action.setCompletedDate(action.getStartedDate());
action.setStartedDateCode(CommunityCodeValues.DATE_FORMAT_DATETIME.value);
action.setCompletedDateCode(CommunityCodeValues.DATE_FORMAT_DATETIME.value);
action = actionService.create(action);
assert (action != null);
log.debug("Added action {}: {}", action.getActionNameCode(), action.getNote());
} else {
log.trace("Not recording name change {} {}", oldNumber, newNumber);
}
}
private void recordAccessionNumber(long accessionId, String accessionNumber, String accessionNumberPart1) {
log.debug("Registering accession number: {}", accessionNumber);
var systemInventory = inventoryRepository.getSystemInventory(new Accession(accessionId));
NameGroup nameGroup = null;
if (StringUtils.isNotBlank(accessionNumberPart1)) {
// link it with the name group
nameGroup = nameGroupRepository.findByGroupName(accessionNumberPart1);
log.trace("Name group {} {}", accessionNumberPart1, nameGroup);
}
// Check if the name exists
var qAIN = QAccessionInvName.accessionInvName;
var predicate =
// inventory
qAIN.inventory().eq(systemInventory)
// plant name
.and(qAIN.plantName.eq(accessionNumber))
// category code
.and(qAIN.categoryCode.eq(CommunityCodeValues.ACCESSION_NAME_TYPE_SITE.value))
// name group
.and(nameGroup == null ? qAIN.nameGroup().isNull() : qAIN.nameGroup().eq(nameGroup));
if (! accessionInvNameRepository.exists(predicate)) {
AccessionInvName invName = new AccessionInvName();
invName.setPlantName(accessionNumber);
invName.setPlantNameRank(1080);
invName.setCategoryCode(CommunityCodeValues.ACCESSION_NAME_TYPE_SITE.value);
invName.setInventory(systemInventory);
invName.setNameGroup(nameGroup);
var ain = accessionInvNameService.create(invName);
assert(ain != null);
} else {
log.trace("Name already exists");
}
}
private void recordAccessionSpeciesChangesIfNeeded(long accessionId, TaxonomySpecies oldTs, TaxonomySpecies newTs) {
log.debug("recordAccessionSpeciesChangesIfNeeded: orig={} curr={}", oldTs, newTs);
if (oldTs != null && !Objects.equals(oldTs.getId(), newTs.getId())) {
AccessionInvAnnotation annotation = new AccessionInvAnnotation();
annotation.setAnnotationTypeCode(CommunityCodeValues.ANNOTATION_TYPE_RE_IDENT.value);
annotation.setInventory(inventoryRepository.getSystemInventory(new Accession(accessionId)));
annotation.setOldTaxonomySpecies(oldTs);
annotation.setNewTaxonomySpecies(newTs);
annotation.setAnnotationDate(new Date());
annotation.setAnnotationDateCode(CommunityCodeValues.DATE_FORMAT_DATETIME.value);
var aia = accessionInvAnnotationService.create(annotation);
assert(aia != null);
} else {
log.trace("No change detected old={} new={}", oldTs, newTs);
}
}
private void recordReceivedAccessionSpecies(Accession accession) {
assert(accession.getId() != null);
AccessionInvAnnotation annotation = new AccessionInvAnnotation();
annotation.setAnnotationTypeCode(CommunityCodeValues.ANNOTATION_TYPE_RECEIVED.value);
annotation.setInventory(inventoryRepository.getSystemInventory(accession));
annotation.setAnnotationDate(new Date());
annotation.setAnnotationDateCode(CommunityCodeValues.DATE_FORMAT_DATETIME.value);
annotation.setNewTaxonomySpecies(accession.getTaxonomySpecies());
var aia = accessionInvAnnotationService.create(annotation);
assert(aia != null);
}
}