package org.nextprot.api.core.service.impl; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import org.apache.commons.lang.StringUtils; import org.nextprot.api.commons.constants.AnnotationCategory; import org.nextprot.api.commons.constants.IdentifierOffset; import org.nextprot.api.commons.constants.TerminologyCv; import org.nextprot.api.core.dao.AnnotationDAO; import org.nextprot.api.core.dao.BioPhyChemPropsDao; import org.nextprot.api.core.dao.PtmDao; import org.nextprot.api.core.domain.CvTerm; import org.nextprot.api.core.domain.ExperimentalContext; import org.nextprot.api.core.domain.Feature; import org.nextprot.api.core.domain.Isoform; import org.nextprot.api.core.domain.annotation.Annotation; import org.nextprot.api.core.domain.annotation.AnnotationEvidence; import org.nextprot.api.core.domain.annotation.AnnotationEvidenceProperty; import org.nextprot.api.core.domain.annotation.AnnotationIsoformSpecificity; import org.nextprot.api.core.domain.annotation.AnnotationProperty; import org.nextprot.api.core.service.AnnotationService; import org.nextprot.api.core.service.AntibodyMappingService; import org.nextprot.api.core.service.DbXrefService; import org.nextprot.api.core.service.ExperimentalContextDictionaryService; import org.nextprot.api.core.service.InteractionService; import org.nextprot.api.core.service.IsoformService; import org.nextprot.api.core.service.PeptideMappingService; import org.nextprot.api.core.service.TerminologyService; import org.nextprot.api.core.utils.TerminologyUtils; import org.nextprot.api.core.utils.annot.AnnotationUtils; import org.nextprot.api.core.utils.graph.OntologyDAG; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.nextprot.api.annotation.builder.statement.service.StatementService; import javax.annotation.Nullable; //import java.util.*; import java.util.function.Predicate; @Service public class AnnotationServiceImpl implements AnnotationService { @Autowired private AnnotationDAO annotationDAO; @Autowired private PtmDao ptmDao; @Autowired private DbXrefService xrefService; @Autowired private InteractionService interactionService; @Autowired private BioPhyChemPropsDao bioPhyChemPropsDao; @Autowired private IsoformService isoformService; @Autowired private PeptideMappingService peptideMappingService; @Autowired private AntibodyMappingService antibodyMappingService; @Autowired private StatementService statementService; @Autowired private TerminologyService terminologyService; @Autowired private ExperimentalContextDictionaryService experimentalContextDictionaryService; @Override @Cacheable("annotations") public List<Annotation> findAnnotations(String entryName) { return findAnnotations(entryName,false); } /** * pam: useful for test AnnotationServiceTest to work and for other tests */ @Override public List<Annotation> findAnnotationsExcludingBed(String entryName) { return findAnnotations(entryName,true); } private List<Annotation> findAnnotations(String entryName, boolean ignoreStatements) { Preconditions.checkArgument(entryName != null, "The annotation name should be set wit #withName(...)"); List<Annotation> annotations = annotationDAO.findAnnotationsByEntryName(entryName); if (! annotations.isEmpty()) { List<Long> annotationIds = Lists.transform(annotations, new AnnotationFunction()); // Evidences List<AnnotationEvidence> evidences = annotationDAO.findAnnotationEvidencesByAnnotationIds(annotationIds); Multimap<Long, AnnotationEvidence> evidencesByAnnotationId = Multimaps.index(evidences, new AnnotationEvidenceFunction()); for (Annotation annotation : annotations) { annotation.setEvidences(new ArrayList<>(evidencesByAnnotationId.get(annotation.getAnnotationId()))); } // Evidences properties if(!evidences.isEmpty()){ List<Long> evidencesIds = Lists.transform(evidences, new AnnotationEvidenceIdFunction()); List<AnnotationEvidenceProperty> evidenceProperties = annotationDAO.findAnnotationEvidencePropertiesByEvidenceIds(evidencesIds); Multimap<Long, AnnotationEvidenceProperty> propertiesByEvidenceId = Multimaps.index(evidenceProperties, new AnnotationEvidencePropertyFunction()); for (AnnotationEvidence evidence : evidences) { evidence.setProperties(new ArrayList<>(propertiesByEvidenceId.get(evidence.getEvidenceId()))); } } // Isoforms List<AnnotationIsoformSpecificity> isoforms = annotationDAO.findAnnotationIsoformsByAnnotationIds(annotationIds); Multimap<Long, AnnotationIsoformSpecificity> isoformsByAnnotationId = Multimaps.index(isoforms, new AnnotationIsoformFunction()); for (Annotation annotation : annotations) { annotation.addTargetingIsoforms(new ArrayList<>(isoformsByAnnotationId.get(annotation.getAnnotationId()))); } // Properties List<AnnotationProperty> properties = annotationDAO.findAnnotationPropertiesByAnnotationIds(annotationIds); Multimap<Long, AnnotationProperty> propertiesByAnnotationId = Multimaps.index(properties, new AnnotationPropertyFunction()); for (Annotation annotation : annotations) { annotation.addProperties(propertiesByAnnotationId.get(annotation.getAnnotationId())); } // Removes annotations which do not map to any isoform, // this may happen in case where the annotation has been seen in a peptide and the annotation was propagated to the master, // but we were not able to map to any isoform Iterator<Annotation> annotationsIt = annotations.iterator(); while(annotationsIt.hasNext()){ Annotation a = annotationsIt.next(); if(a.getTargetingIsoformsMap().size() == 0){ annotationsIt.remove(); } } AnnotationUtils.convertRelativeEvidencesToProperties(annotations); // CALIPHOMISC-277 for (Annotation annot : annotations) { refactorDescription(annot); } } annotations.addAll(this.xrefService.findDbXrefsAsAnnotationsByEntry(entryName)); annotations.addAll(this.interactionService.findInteractionsAsAnnotationsByEntry(entryName)); annotations.addAll(this.peptideMappingService.findNaturalPeptideMappingAnnotationsByMasterUniqueName(entryName)); annotations.addAll(this.peptideMappingService.findSyntheticPeptideMappingAnnotationsByMasterUniqueName(entryName)); annotations.addAll(this.antibodyMappingService.findAntibodyMappingAnnotationsByUniqueName(entryName)); annotations.addAll(bioPhyChemPropsToAnnotationList(entryName, this.bioPhyChemPropsDao.findPropertiesByUniqueName(entryName))); if (!ignoreStatements) annotations = AnnotationUtils.mapReduceMerge(statementService.getAnnotations(entryName), annotations); // post-processing of annotations updateIsoformsDisplayedAsSpecific(annotations, entryName); updateVariantsRelatedToDisease(annotations); updateSubcellularLocationTermNameWithAncestors(annotations); updateMiscRegionsRelatedToInteractions(annotations); //returns a immutable list when the result is cache-able (this prevents modifying the cache, since the cache returns a reference) return new ImmutableList.Builder<Annotation>().addAll(annotations).build(); } private void updateSubcellularLocationTermNameWithAncestors(List<Annotation> annotations) { //long t0 = System.currentTimeMillis(); System.out.println("updateSubcellularLocationTermNameWithAncestors..."); for (Annotation annot: annotations) { if (AnnotationCategory.SUBCELLULAR_LOCATION == annot.getAPICategory()) { CvTerm t = terminologyService.findCvTermByAccession(annot.getCvTermAccessionCode()); List<CvTerm> terms = TerminologyUtils.getOnePathToRootTerm(t.getAccession(), terminologyService); String longName = AnnotationUtils.getTermNameWithAncestors(annot, terms); AnnotationProperty prop = new AnnotationProperty(); prop.setAnnotationId(annot.getAnnotationId()); prop.setName("long-name"); prop.setValue(String.valueOf(longName)); annot.addProperty(prop); String descr = annot.getDescription(); if (descr != null && !annot.getCvTermName().equals(descr)) { // 3 cases: "Main location", "Additional localtion" or "Note=..." if (descr.startsWith("Note=")) descr=descr.substring(5); prop = new AnnotationProperty(); prop.setAnnotationId(annot.getAnnotationId()); prop.setName("name-modifier"); prop.setValue(String.valueOf(descr)); annot.addProperty(prop); } } } //System.out.println("updateSubcellularLocationTermNameWithAncestors DONE in " + (System.currentTimeMillis() - t0) + "ms"); } private void updateVariantsRelatedToDisease(List<Annotation> annotations) { Map<Long,ExperimentalContext> ecMap = experimentalContextDictionaryService.getAllExperimentalContexts(); //long t0 = System.currentTimeMillis(); System.out.println("updateVariantsRelatedToDisease..."); // add property if annotation is a variant related to disease for (Annotation annot: annotations) { if (AnnotationCategory.VARIANT == annot.getAPICategory()) { boolean result = AnnotationUtils.isVariantRelatedToDiseaseProperty(annot, ecMap); AnnotationProperty prop = new AnnotationProperty(); prop.setAnnotationId(annot.getAnnotationId()); prop.setName("disease-related"); prop.setValue(String.valueOf(result)); annot.addProperty(prop); } } //System.out.println("updateVariantsRelatedToDisease DONE in " + (System.currentTimeMillis() - t0) + "ms"); } private void updateMiscRegionsRelatedToInteractions(List<Annotation> annotations) { // add property if annotation is a misc region and it is related to interaction for (Annotation annot: annotations) { if (AnnotationCategory.MISCELLANEOUS_REGION == annot.getAPICategory()) { boolean result = AnnotationUtils.isMiscRegionRelatedToInteractions(annot); AnnotationProperty prop = new AnnotationProperty(); prop.setAnnotationId(annot.getAnnotationId()); prop.setName("interaction-related"); prop.setValue(String.valueOf(result)); annot.addProperty(prop); } } } private void updateIsoformsDisplayedAsSpecific(List<Annotation> annotations, String entryName) { //long t0 = System.currentTimeMillis(); System.out.println("updateIsoformsDisplayedAsSpecific..."); List<Isoform> isoforms = isoformService.findIsoformsByEntryName(entryName); int entryIsoformCount=isoforms.size(); for (Annotation annot: annotations) { annot.setIsoformsDisplayedAsSpecific(AnnotationUtils.computeIsoformsDisplayedAsSpecific(annot, entryIsoformCount)); } //System.out.println("updateIsoformsDisplayedAsSpecific DONE in " + (System.currentTimeMillis() - t0) + "ms"); } private List<Annotation> bioPhyChemPropsToAnnotationList(String entryName, List<AnnotationProperty> props) { List<Annotation> annotations = new ArrayList<>(props.size()); List<Isoform> isoforms = isoformService.findIsoformsByEntryName(entryName); for(AnnotationProperty property : props){ Annotation annotation = new Annotation(); AnnotationCategory model = AnnotationCategory.getByDbAnnotationTypeName(property.getName()); String description = property.getValue(); annotation.setAnnotationId(property.getAnnotationId() + IdentifierOffset.BIOPHYSICOCHEMICAL_ANNOTATION_OFFSET); annotation.setCategory(model.getDbAnnotationTypeName()); annotation.setDescription(description); annotation.setEvidences(new ArrayList<>()); annotation.setQualityQualifier("GOLD"); annotation.setUniqueName(entryName + "_" + model.getApiTypeName()); annotation.addTargetingIsoforms(newAnnotationIsoformSpecificityList(annotation.getAnnotationId(), isoforms)); annotations.add(annotation); } return annotations; } private List<AnnotationIsoformSpecificity> newAnnotationIsoformSpecificityList(long annotationId, List<Isoform> isoforms) { List<AnnotationIsoformSpecificity> specs = new ArrayList<>(isoforms.size()); for (Isoform isoform : isoforms) { AnnotationIsoformSpecificity spec = new AnnotationIsoformSpecificity(); spec.setAnnotationId(annotationId); spec.setIsoformAccession(isoform.getIsoformAccession()); spec.setSpecificity("UNKNOWN"); specs.add(spec); } return specs; } @Override public List<Feature> findPtmsByMaster(String uniqueName) { return this.ptmDao.findPtmsByEntry(uniqueName); } @Override public List<Feature> findPtmsByIsoform(String isoformUniqueName) { String masterUniqueName = extractMasterUniqueName(isoformUniqueName); List<Feature> ptms = this.ptmDao.findPtmsByEntry(masterUniqueName); return filterByIsoform(isoformUniqueName, ptms); } @Override public Predicate<Annotation> buildCvTermAncestorPredicate(String ancestorAccession) { return new CvTermAncestorPredicate(terminologyService.findCvTermByAccession(ancestorAccession)); } @Override public Predicate<Annotation> buildPropertyPredicate(String propertyName, @Nullable String propertyValueOrAccession) { if (propertyName != null && !propertyName.isEmpty()) { Predicate<Annotation> propExistencePredicate = annotation -> annotation.getPropertiesMap().containsKey(propertyName); if (propertyValueOrAccession != null && !propertyValueOrAccession.isEmpty()) { return propExistencePredicate.and(annotation -> { Collection<AnnotationProperty> props = annotation.getPropertiesByKey(propertyName); return props.stream().anyMatch(annotationProperty -> propertyValueOrAccession.equals(annotationProperty.getValue()) || propertyValueOrAccession.equals(annotationProperty.getAccession())); }); } return propExistencePredicate; } return annotation -> true; } private class CvTermAncestorPredicate implements Predicate<Annotation> { private final CvTerm ancestor; private final OntologyDAG dag; private CvTermAncestorPredicate(CvTerm ancestor) { this.ancestor = ancestor; dag = terminologyService.findOntologyGraph(TerminologyCv.valueOf(ancestor.getOntology())); } @Override public boolean test(Annotation annotation) { try { return annotation.getCvTermAccessionCode() != null && (annotation.getCvTermAccessionCode().equals(ancestor.getAccession()) || dag.isAncestorOf(ancestor.getId(), dag.getCvTermIdByAccession(annotation.getCvTermAccessionCode()))); } catch (OntologyDAG.NotFoundNodeException e) { return false; } } } // Function class to filter using guava /////////////////////////////////////////////////////////////////////////////// private class AnnotationPropertyFunction implements Function<AnnotationProperty, Long> { public Long apply(AnnotationProperty annotation) { return annotation.getAnnotationId(); } } private class AnnotationIsoformFunction implements Function<AnnotationIsoformSpecificity, Long> { public Long apply(AnnotationIsoformSpecificity annotation) { return annotation.getAnnotationId(); } } private class AnnotationEvidenceFunction implements Function<AnnotationEvidence, Long> { public Long apply(AnnotationEvidence annotation) { return annotation.getAnnotationId(); } } private class AnnotationEvidencePropertyFunction implements Function<AnnotationEvidenceProperty, Long> { public Long apply(AnnotationEvidenceProperty property) { return property.getEvidenceId(); } } private class AnnotationFunction implements Function<Annotation, Long> { public Long apply(Annotation annotation) { return annotation.getAnnotationId(); } } private class AnnotationEvidenceIdFunction implements Function<AnnotationEvidence, Long> { public Long apply(AnnotationEvidence evidence) { return evidence.getEvidenceId(); } } // Refactor the descriptions /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// private static void refactorDescription(Annotation annotation) { String category = annotation.getCategory(); if (annotation.getDescription() == null || annotation.getDescription().indexOf(':') == 1) { annotation.setDescription(annotation.getCvTermName()); } if (category != null) { if ("sequence caution".equals(category)) { setSequenceCautionDescription(annotation); } else if ("go molecular function".equals(category) || "go cellular component".equals(category) || "go biological process".equals(category)) { setGODescription(annotation); } else if ("sequence conflict".equals(category) || "sequence variant".equals(category) || "mutagenesis site".equals(category)) { setVariantDescription(annotation); } } } private static void setSequenceCautionDescription(Annotation annotation) { SortedSet<String> acs = new TreeSet<>(); for (AnnotationProperty ap : annotation.getProperties()) { if ("differing sequence".equals(ap.getName())) acs.add(ap.getAccession()); } StringBuilder sb = new StringBuilder("The sequence").append(acs.size() > 1 ? "s" : ""); for (String emblAc : acs) { sb.append(" ").append(emblAc); } sb.append(" differ").append(acs.size() == 1 ? "s" : "").append(" from that shown."); // Beginning of the sentence finish, then: List<AnnotationProperty> conflictTypeProps = new ArrayList<>(); for (AnnotationProperty ap : annotation.getProperties()) { if ("conflict type".equals(ap.getName())) conflictTypeProps.add(ap); } SortedSet<AnnotationProperty> sortedPositions = getSortedPositions(annotation); if (conflictTypeProps != null && !conflictTypeProps.isEmpty()) { sb.append(" Reason:"); for (AnnotationProperty prop : conflictTypeProps) { sb.append(" ").append(prop.getValue()); } if (!sortedPositions.isEmpty()) { sb.append(" at position").append(sortedPositions.size() > 1 ? "s" : ""); for (AnnotationProperty p : sortedPositions) { sb.append(" ").append(p.getValue()); } } sb.append("."); } if (StringUtils.isNotEmpty(annotation.getDescription())) { sb.append(" ").append(annotation.getDescription()); } annotation.setDescription(sb.toString()); } private static SortedSet<AnnotationProperty> getSortedPositions(Annotation annotation) { SortedSet<AnnotationProperty> sortedPositions = new TreeSet<>((p1, p2) -> Integer.valueOf(p1.getValue()).compareTo(Integer.valueOf(p2.getValue()))); List<AnnotationProperty> conflictPositions = new ArrayList<>(); for (AnnotationProperty ap : annotation.getProperties()) { if ("position".equals(ap.getName())) conflictPositions.add(ap); } if (conflictPositions != null && !conflictPositions.isEmpty()) { sortedPositions.addAll(conflictPositions); } return sortedPositions; } private static void setVariantDescription(Annotation annotation) { if (annotation.getVariant() != null && annotation.getVariant().getVariant().isEmpty()) { String description = annotation.getDescription(); annotation.setDescription("Missing " + (description==null ? "": description)); } } private static void setGODescription(Annotation annotation) { //Example if the cv term is : nuclear proteasome complex and there is a GO term of go_qualifier, the description changes to: //Then the description Colocalizes with nuclear proteasome complex for (AnnotationEvidence evidence : annotation.getEvidences()) { if ("evidence".equals(evidence.getResourceAssociationType())) { String goqualifier=evidence.getGoQualifier(); if (goqualifier != null && !goqualifier.isEmpty()) { String description = StringUtils.capitalize(goqualifier.replaceAll("_", " ") + " ") + annotation.getDescription(); annotation.setDescription(description); break; } } } } private List<Feature> filterByIsoform(String isoformUniqueName, List<Feature> annotations) { List<Feature> filteredFeatures = new ArrayList<>(); for (Feature f : annotations) { if (f.getIsoformAccession().equalsIgnoreCase(isoformUniqueName)) { filteredFeatures.add(f); } } return filteredFeatures; } private String extractMasterUniqueName(String isoformUniqueName) { if (isoformUniqueName.indexOf('-') < 1) { throw new InvalidParameterException(String.format( "Invalid isoform accession [%s]", isoformUniqueName)); } return isoformUniqueName.substring(0, isoformUniqueName.indexOf('-')); } }