package org.nextprot.api.core.service.impl;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.nextprot.api.annotation.builder.statement.dao.SimpleWhereClauseQueryDSL;
import com.nextprot.api.annotation.builder.statement.dao.StatementDao;
import org.apache.log4j.Logger;
import org.nextprot.api.commons.dao.MasterIdentifierDao;
import org.nextprot.api.core.dao.AuthorDao;
import org.nextprot.api.core.dao.DbXrefDao;
import org.nextprot.api.core.dao.PublicationDao;
import org.nextprot.api.core.domain.DbXref;
import org.nextprot.api.core.domain.Publication;
import org.nextprot.api.core.domain.PublicationAuthor;
import org.nextprot.api.core.domain.PublicationDbXref;
import org.nextprot.api.core.service.DbXrefService;
import org.nextprot.api.core.service.PublicationService;
import org.nextprot.commons.statements.StatementField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class PublicationServiceImpl implements PublicationService {
private static final Logger LOGGER = Logger.getLogger(PublicationServiceImpl.class);
@Autowired private MasterIdentifierDao masterIdentifierDao;
@Autowired private PublicationDao publicationDao;
@Autowired private StatementDao statementDao;
@Autowired private AuthorDao authorDao;
@Autowired private DbXrefDao dbXrefDao;
@Autowired private DbXrefService dbXrefService;
@Cacheable("publications-get-by-id")
public Publication findPublicationById(long id) {
Publication publication = this.publicationDao.findPublicationById(id); // Basic fields
loadAuthorsAndXrefs(publication); // add non-basic fields to object
return publication;
}
@Override
public List<Publication> findPublicationByTitle(String title) {
return publicationDao.findPublicationByTitle(title);
}
/**
* TO REMOVE
*/
@Override
public List<Publication> findPublicationsByMasterId(Long masterId) {
List<Publication> publications = this.publicationDao.findSortedPublicationsByMasterId(masterId);
publications.forEach(this::loadAuthorsAndXrefs);
return publications;
}
@Override
@Cacheable("publications")
public List<Publication> findPublicationsByMasterUniqueName(String uniqueName) {
List<Publication> publications = publicationDao.findSortedPublicationsByMasterId(masterIdentifierDao.findIdByUniqueName(uniqueName));
Map<Long, List<PublicationDbXref>> npPublicationsXrefs = updateMissingPublicationFields(publications);
// Getting publications from nx flat database
List<Publication> nxflatPublications = new ArrayList<>();
Arrays.asList("PubMed", "DOI").stream().forEach(db -> {
List<String> referenceIds = this.statementDao.findAllDistinctValuesforFieldWhereFieldEqualsValues(
StatementField.REFERENCE_ACCESSION,
new SimpleWhereClauseQueryDSL(StatementField.ENTRY_ACCESSION, uniqueName),
new SimpleWhereClauseQueryDSL(StatementField.REFERENCE_DATABASE, db));
nxflatPublications.addAll(getPublicationsFromDBReferenceIds(referenceIds, db, npPublicationsXrefs));
});
updateMissingPublicationFields(nxflatPublications);
publications.addAll(nxflatPublications);
final Comparator<Publication> comparator = new PublicationComparatorAsc(Publication::getPublicationYear).reversed()
.thenComparing((pub1, pub2) -> pub1.getPublicationType().compareTo(pub2.getPublicationType()))
.thenComparing(new PublicationComparatorAsc(Publication::getPublicationLocatorName))
.thenComparing(new PublicationComparatorAsc(Publication::getVolume, PublicationComparatorAsc.FieldType.NUMBER_TYPE))
.thenComparing(new PublicationComparatorAsc(Publication::getFirstPage, PublicationComparatorAsc.FieldType.NUMBER_TYPE));
// sort according to order with criteria defined in publication-sorted-for-master.sql
Collections.sort(publications, comparator);
//returns a immutable list when the result is cacheable (this prevents modifying the cache, since the cache returns a reference) copy on read and copy on write is too much time consuming
return new ImmutableList.Builder<Publication>().addAll(publications).build();
}
private Map<Long, List<PublicationDbXref>> updateMissingPublicationFields(List<Publication> publications) {
List<Long> publicationIds = publications.stream().map(Publication::getPublicationId).collect(Collectors.toList());
Map<Long, List<PublicationAuthor>> authorMap = authorDao.findAuthorsByPublicationIds(publicationIds).stream()
.collect(Collectors.groupingBy(PublicationAuthor::getPublicationId));
Map<Long, List<PublicationDbXref>> xrefMap = dbXrefService.findDbXRefByPublicationIds(publicationIds).stream()
.collect(Collectors.groupingBy(PublicationDbXref::getPublicationId));
for (Publication publication : publications) {
setAuthorsAndEditors(publication, authorMap.get(publication.getPublicationId()));
setXrefs(publication, xrefMap.get(publication.getPublicationId()));
}
return xrefMap;
}
/**
* Get all publications not found in npPublication from pubmedids
* @param nxflatPubmedIds entry name
* @param npPublicationXrefs needed to avoid loading pubmed id publication multiple times
* @return
*/
private List<Publication> getPublicationsFromDBReferenceIds(List<String> nxflatReferenceIds, String referenceDatabase, Map<Long, List<PublicationDbXref>> npPublicationXrefs) {
List<Publication> nxflatPublications = new ArrayList<>();
// Filtering publications which pubmed was not already found in np publications
List<Long> foundPublicationIds = npPublicationXrefs.keySet().stream()
.filter(pubid -> npPublicationXrefs.get(pubid).stream().anyMatch(xref -> nxflatReferenceIds.contains(xref.getAccession())))
.collect(Collectors.toList());
nxflatReferenceIds.stream()
.filter(pubmed -> pubmed != null)
.forEach(pubmed -> {
Publication pub = this.publicationDao.findPublicationByDatabaseAndAccession(referenceDatabase, pubmed);
if (pub == null) {
LOGGER.warn("Pubmed " + pubmed + " cannot be found");
} else if (!foundPublicationIds.contains(pub.getPublicationId())) {
nxflatPublications.add(pub);
}
}
);
return nxflatPublications;
}
@Autowired
public void setPublicationDao(PublicationDao publicationDao) {
this.publicationDao = publicationDao;
}
@Override
public Publication findPublicationByMD5(String md5) {
Publication publication = this.publicationDao.findPublicationByMD5(md5);
loadAuthorsAndXrefs(publication);
return publication;
}
@Override
public List<Long> findAllPublicationIds() {
return publicationDao.findAllPublicationsIds();
}
private void loadAuthorsAndXrefs(Publication publication){
long publicationId = publication.getPublicationId();
setAuthorsAndEditors(publication, authorDao.findAuthorsByPublicationId(publicationId));
setXrefs(publication, dbXrefDao.findDbXRefsByPublicationId(publicationId));
}
/**
* Extract editors from authors then set authors, editors
*/
private void setAuthorsAndEditors(Publication publication, Collection<PublicationAuthor> authorsAndEditors) {
if (authorsAndEditors != null) {
Set<PublicationAuthor> authorsAndEditorSet = new TreeSet<>(authorsAndEditors);
publication.setAuthors(new TreeSet<>(Sets.filter(authorsAndEditorSet, pa -> pa != null && !pa.isEditor())));
publication.setEditors(new TreeSet<>(Sets.filter(authorsAndEditorSet, pa -> pa != null && pa.isEditor())));
} else {
publication.setAuthors(new TreeSet<>());
publication.setEditors(new TreeSet<>());
}
}
private void setXrefs(Publication publication, Collection<? extends DbXref> xrefs){
if (xrefs == null) {
publication.setDbXrefs(new HashSet<>());
} else {
publication.setDbXrefs(new HashSet<>(xrefs));
}
}
@Override
@Cacheable("publications-by-id-and-accession")
public Publication findPublicationByDatabaseAndAccession(String database, String accession) {
return publicationDao.findPublicationByDatabaseAndAccession(database, accession);
}
/**
* Base class for comparing Publications in ascending order of String field
*/
private static class PublicationComparatorAsc implements Comparator<Publication> {
private enum FieldType {
STRING_TYPE, NUMBER_TYPE
}
private final Function<Publication, String> toFieldStringFunc;
private final FieldType fieldType;
PublicationComparatorAsc(Function<Publication, String> toFieldStringFunc) {
this(toFieldStringFunc, FieldType.STRING_TYPE);
}
/**
* @param toFieldStringFunc accept a Publication and produce a String to be compare
* @param fieldType the field type
*/
PublicationComparatorAsc(Function<Publication, String> toFieldStringFunc, FieldType fieldType) {
Preconditions.checkNotNull(toFieldStringFunc);
Preconditions.checkNotNull(fieldType);
this.toFieldStringFunc = toFieldStringFunc;
this.fieldType = fieldType;
}
@Override
public int compare(Publication p1, Publication p2) {
String stringField1 = toFieldStringFunc.apply(p1);
String stringField2 = toFieldStringFunc.apply(p2);
if (Objects.equals(stringField1, stringField2) ) {
return 0;
}
if (stringField1 == null || stringField1.isEmpty()) {
return 1;
}
if (stringField2 == null || stringField2.isEmpty()) {
return -1;
}
return (fieldType == FieldType.STRING_TYPE) ? doStringComparison(stringField1, stringField2) : doIntComparison(stringField1, stringField2);
}
private int doStringComparison(String string1, String string2) {
return string1.compareTo(string2);
}
private int doIntComparison(String string1, String string2) {
boolean isInt1 = string1.matches("\\d+");
boolean isInt2 = string2.matches("\\d+");
// compare ints
if (isInt1 && isInt2) {
return Integer.compare(Integer.parseInt(string1), Integer.parseInt(string2));
}
else if (isInt1) {
return -1;
}
else if (isInt2) {
return 1;
}
// compare strings
return doStringComparison(string1, string2);
}
}
}