package org.gmod.schema.mapped; import org.genedb.db.analyzers.AllNamesAnalyzer; import org.genedb.db.dao.CvDao; import org.genedb.db.dao.GeneralDao; import org.genedb.db.dao.SequenceDao; import org.genedb.db.helpers.LocationBridge; import org.genedb.util.SequenceUtils; import org.gmod.schema.feature.Gap; import org.gmod.schema.feature.ProteinMatch; import org.gmod.schema.feature.Region; import org.gmod.schema.utils.CollectionUtils; import org.gmod.schema.utils.SimilarityI; import org.gmod.schema.utils.StrandedLocation; import org.apache.log4j.Logger; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; import org.hibernate.search.annotations.Analyzer; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.FieldBridge; import org.hibernate.search.annotations.Index; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.IndexedEmbedded; import org.hibernate.search.annotations.Store; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.orm.hibernate3.SessionFactoryUtils; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.testng.v6.Lists; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.persistence.Basic; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; import javax.persistence.Transient; @Configurable @Entity /* * We have wondered whether to add the annotation * * @Proxy(lazy=false) * * here. This would prevent proxy objects being created for Feature * entities - though not for subclasses. For example, if we had that * annotation then session.load(Feature.class, featureId) would return * a proper Feature object of the appropriate class, rather than a * proxy subclass of Feature. This would allow instanceof checks to * work as expected. * * The danger is that we might be implicitly relying on proxy creation * without realising it, and that this change might cause unanticipated * problems. Instead we have made a couple of other changes that will * alleviate problems in practice: using session.get rather than session.load * in genedb-web's IndexSynchroniser; and adding explicit checks for * proxies to the getSubjectFeature and getObjectFeature methods of * FeatureRelationship. * * This decision should be kept under review. * * -rh11, 2009-05-06 * * Note that some methods are synchronized on internal locks. However most of the * code base assumes a single-threading model. So it's safer to synchronize externally * if necessary. * * * */ @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "type_id") @Table(name = "feature") @Indexed public abstract class Feature implements java.io.Serializable, HasPubsAndDbXRefs { private static final long serialVersionUID = -2327481898902900029L; @Autowired protected transient CvDao cvDao; @Autowired protected transient GeneralDao generalDao; @Autowired protected transient SequenceDao sequenceDao; @Autowired @Qualifier("sessionFactory") protected transient SessionFactory sessionFactory; @GenericGenerator(name = "generator", strategy = "seqhilo", parameters = { @Parameter(name = "max_lo", value = "100"), @Parameter(name = "sequence", value = "feature_feature_id_seq") }) @Id @GeneratedValue(generator = "generator") @Column(name = "feature_id", unique = true, nullable = false, insertable = true, updatable = false) @DocumentId private int featureId; @ManyToOne(cascade = {}) @JoinColumn(name = "organism_id", unique = false, nullable = false, insertable = true, updatable = true) @IndexedEmbedded(depth = 1) private Organism organism; @ManyToOne(cascade = {}) @JoinColumn(name = "type_id", unique = false, nullable = false, insertable = false, updatable = false) @IndexedEmbedded(depth = 2) private CvTerm type; @Column(name = "name", unique = false, nullable = true, insertable = true, updatable = true) @Field(index = Index.UN_TOKENIZED, store = Store.YES) private String name; @Column(name = "uniquename", unique = false, nullable = false, insertable = true, updatable = true) @Field(index = Index.UN_TOKENIZED, store = Store.YES) private String uniqueName; @Column(name = "seqlen", unique = false, nullable = true, insertable = true, updatable = true) private Integer seqLen = -1; @Column(name = "md5checksum", unique = false, nullable = true, insertable = true, updatable = true, length = 32) private String md5Checksum; @Column(name = "is_analysis", unique = false, nullable = false, insertable = true, updatable = true) @Field(index = Index.UN_TOKENIZED, store = Store.NO) private boolean analysis; @Column(name = "is_obsolete", unique = false, nullable = false, insertable = true, updatable = true) @Field(index = Index.UN_TOKENIZED, store = Store.YES) private boolean obsolete; @Column(name = "timeaccessioned", unique = false, nullable = false, insertable = true, updatable = true, length = 29) private Timestamp timeAccessioned; @Column(name = "timelastmodified", unique = false, nullable = false, insertable = true, updatable = true, length = 29) private Timestamp timeLastModified; // ------------------------------------------------------------------------------- // Unsorted properties below here @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, mappedBy = "feature") private Collection<Phylonode> phylonodes; @ManyToOne(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY) @JoinColumn(name = "dbxref_id", unique = false, nullable = true, insertable = true, updatable = true) private DbXRef dbXRef; @Column(name = "residues", unique = false, nullable = true, insertable = true, updatable = true) @Basic(fetch = FetchType.LAZY) private String residues; @OneToMany(cascade = {}, fetch = FetchType.LAZY, mappedBy = "sourceFeature") private Set<FeatureLoc> featureLocsForSrcFeatureId; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "objectFeature") protected Set<FeatureRelationship> featureRelationshipsForObjectId; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "subjectFeature") protected Set<FeatureRelationship> featureRelationshipsForSubjectId; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "feature") private Set<FeatureDbXRef> featureDbXRefs; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.EAGER, mappedBy = "feature") @Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN}) @OrderBy("locGroup ASC, rank ASC") private List<FeatureLoc> featureLocs; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "feature") @Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN}) private Set<FeatureCvTerm> featureCvTerms; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "feature") @Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN}) private Set<FeatureProp> featureProps; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "feature") @Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN}) private Set<FeaturePub> featurePubs; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "feature") private Set<AnalysisFeature> analysisFeatures; @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "feature") private Set<FeatureSynonym> featureSynonyms; @Transient private Logger logger = Logger.getLogger(Feature.class); // Constructors /** default constructor */ public Feature() { // Deliberately empty } /** minimal constructor */ public Feature(Organism organism, CvTerm cvTerm, String uniqueName, boolean analysis, boolean obsolete, Timestamp timeAccessioned, Timestamp timeLastModified) { this.organism = organism; this.type = cvTerm; this.uniqueName = uniqueName; this.analysis = analysis; this.obsolete = obsolete; this.timeAccessioned = timeAccessioned; this.timeLastModified = timeLastModified; } protected Feature(Organism organism, String uniqueName, boolean analysis, boolean obsolete, Timestamp timeAccessioned, Timestamp timeLastModified) { // Note that this constructor does not initialise type. The type_id column // will still be correctly populated when this object is persisted, because it's // the discriminator this.organism = organism; this.uniqueName = uniqueName; this.analysis = analysis; this.obsolete = obsolete; this.timeAccessioned = timeAccessioned; this.timeLastModified = timeLastModified; } // Property accessors public int getFeatureId() { return this.featureId; } public Organism getOrganism() { return this.organism; } public CvTerm getType() { return this.type; } void setType(CvTerm type) { this.type = type; } public DbXRef getDbXRef() { return this.dbXRef; } public void setDbXRef(DbXRef dbXRef) { this.dbXRef = dbXRef; } /** * Get the human-readable name of the feature, such as the gene name * * @return the name, may be null */ public String getName() { return this.name; } /** * Set the human-readable form of the feature eg the gene name * * @param name the human-readable name */ public void setName(String name) { this.name = name; } /** * Fetch the unique name (systematic id) for the feature * * @return the unique name, not null */ public String getUniqueName() { return this.uniqueName; } /** * Set the unique name (systematic id) for the feature * * @param uniqueName the unique name, not null * TODO Should update synonyms table */ public void setUniqueName(String uniqueName) { if (uniqueName == null) { throw new NullPointerException("setUniqueName: the unique name cannot be null"); } this.uniqueName = uniqueName; } public String getResidues() { return this.residues; } /** * Fetch a subset of the sequence (may be lazy) * * @param min the lower bound, in interbase coordinates * @param max the upper bound, in interbase coordinates * @return */ public String getResidues(int min, int max) { return getResidues(min, max, false); } /** * Fetch a substring of the sequence, possibly reverse-complemented * @param min the lower bound, in interbase coordinates * @param max the upper bound, in interbase coordinates * @param reverseComplement whether to take the reverse complement * @return the subsequence */ public String getResidues(int min, int max, boolean reverseComplement) { String sequence = getResidues().substring(min, max); if (reverseComplement) { return SequenceUtils.reverseComplement(sequence); } return sequence; } public void setResidues(String residues) { this.residues = residues; if (residues == null) { seqLen = 0; md5Checksum = ""; return; } seqLen = residues.length(); this.md5Checksum = calcMD5(this.residues); } /** * Fetch the length of the sequence. Find it from the parent if necessary * * @return the length */ public int getSeqLen() { if (this.seqLen != null && this.seqLen.intValue() == -1 && residues != null) { return getResidues().length(); } if (this.seqLen == null) { return 0; } return this.seqLen.intValue(); } public void setSeqLen(Integer seqLen) { this.seqLen = seqLen; } public String getMd5Checksum() { return this.md5Checksum; } public void setMd5Checksum(String md5Checksum) { this.md5Checksum = md5Checksum; } public boolean isAnalysis() { return this.analysis; } public void setAnalysis(boolean analysis) { this.analysis = analysis; } public boolean isObsolete() { return this.obsolete; } public void setObsolete(boolean obsolete) { this.obsolete = obsolete; } public Date getTimeAccessioned() { return this.timeAccessioned; } /** * Return the last modified timestamp, if the feature has been modified since loading. This is defined as the 'time last modified' = * 'time last accessioned'. * * @return the time last modified, or null if the feature hasn't been modified since creation */ public Timestamp getTimeLastModified() { if (this.timeLastModified.compareTo(this.timeAccessioned) == 0) { return null; } return this.timeLastModified; } public void setTimeLastModified(Timestamp timeLastModified) { this.timeLastModified = timeLastModified; } @Transient private Object featureLocsForSrcFeatureIdLock = new Object(); public Collection<FeatureLoc> getFeatureLocsForSrcFeatureId() { synchronized (featureLocsForSrcFeatureIdLock) { if (featureLocsForSrcFeatureId == null) { featureLocsForSrcFeatureId = new HashSet<FeatureLoc>(); } return featureLocsForSrcFeatureId; } } /** * Returns the unique rank=0 FeatureLoc associated with this feature. Every * feature should have one, so this method will not return null unless * something is wrong in the database. * * @return the unique rank=0 FeatureLoc associated with this feature */ @Transient public FeatureLoc getRankZeroFeatureLoc() { List<FeatureLoc> featureLocs = getFeatureLocs(); if (featureLocs.size() == 0) { logger.error(String.format("getRankZeroFeatureLoc: Feature '%s' has no FeatureLocs", uniqueName)); return null; } for (FeatureLoc featureLoc : featureLocs) { if (featureLoc == null) { logger.warn(String.format("Feature '%s' has a null featureLoc", uniqueName)); } else { return featureLoc; } } logger.error(String.format("Feature '%s' has no non-null featureLocs", uniqueName)); return null; } public Collection<FeatureRelationship> getFeatureRelationshipsForObjectId() { if (featureRelationshipsForObjectId == null) { featureRelationshipsForObjectId = new HashSet<FeatureRelationship>(); } return featureRelationshipsForObjectId; } public Collection<FeatureRelationship> getFeatureRelationshipsForSubjectId() { return (featureRelationshipsForSubjectId = CollectionUtils .safeGetter(featureRelationshipsForSubjectId)); } public void addFeatureRelationshipsForSubjectId( FeatureRelationship featureRelationshipForSubjectId) { if (featureRelationshipsForSubjectId == null) { featureRelationshipsForSubjectId = new HashSet<FeatureRelationship>(); } featureRelationshipForSubjectId.setSubjectFeature(this); this.featureRelationshipsForSubjectId.add(featureRelationshipForSubjectId); } public void addFeatureRelationshipsForObjectId( FeatureRelationship featureRelationshipForObjectId) { if (featureRelationshipsForObjectId == null) { featureRelationshipsForObjectId = new HashSet<FeatureRelationship>(); } featureRelationshipForObjectId.setObjectFeature(this); this.featureRelationshipsForObjectId.add(featureRelationshipForObjectId); } public Collection<FeatureDbXRef> getFeatureDbXRefs() { return Collections.unmodifiableCollection(this.featureDbXRefs); } public void addFeatureDbXRef(FeatureDbXRef featureDbXRef) { if (this.featureDbXRefs == null) { this.featureDbXRefs = new HashSet<FeatureDbXRef>(); } this.featureDbXRefs.add(featureDbXRef); featureDbXRef.setFeature(this); } public FeatureDbXRef addDbXRef(DbXRef dbXRef) { return addDbXRef(dbXRef, true); } public FeatureDbXRef addDbXRef(DbXRef dbXRef, boolean current) { FeatureDbXRef featureDbXRef = new FeatureDbXRef(dbXRef, this, current); addFeatureDbXRef(featureDbXRef); return featureDbXRef; } @Transient private Object featureLocsLock = new Object(); public List<FeatureLoc> getFeatureLocs() { synchronized (featureLocsLock) { return (featureLocs = CollectionUtils.safeGetter(featureLocs)); } } public FeatureLoc getFeatureLoc(int locGroup, int rank) { synchronized (featureLocsLock) { for (FeatureLoc featureLoc: getFeatureLocs()) { if (featureLoc.getLocGroup() == locGroup && featureLoc.getRank() == rank) { return featureLoc; } } } return null; } //Added on 27.8.2009 by nds (remove if redundant) public FeatureLoc getFeatureLocOnThisSrcFeature(Feature srcFeature){ synchronized (featureLocsLock) { for (FeatureLoc featureLoc: getFeatureLocs()) { if (featureLoc.getSourceFeature().getFeatureId() == srcFeature.getFeatureId()) { return featureLoc; } } } return null; } public void addFeatureLoc(FeatureLoc featureLoc) { synchronized (featureLocsLock) { featureLoc.setFeature(this); getFeatureLocs().add(featureLoc); } } protected void removeFeatureLoc(FeatureLoc featureLoc) { synchronized (featureLocsLock) { Iterator<FeatureLoc> it = featureLocs.iterator(); while (it.hasNext()) { if (it.next().getFeatureLocId() == featureLoc.getFeatureLocId()) { logger.trace(String.format("Removing FeatureLoc (ID %d) from feature '%s'", featureLoc.getFeatureLocId(), getUniqueName())); it.remove(); } } } featureLoc.setSourceFeature(null); } @Transient private Object featureCvTermsLock = new Object(); public Collection<FeatureCvTerm> getFeatureCvTerms() { synchronized (featureCvTermsLock) { if (this.featureCvTerms == null) { featureCvTerms = new HashSet<FeatureCvTerm>(); } return this.featureCvTerms; } } public void addFeatureCvTerm(FeatureCvTerm featureCvTerm) { synchronized (featureCvTermsLock) { if (this.featureCvTerms == null) { featureCvTerms = new HashSet<FeatureCvTerm>(); } featureCvTerms.add(featureCvTerm); } } @Transient private Object featurePropsLock = new Object(); public Collection<FeatureProp> getFeatureProps() { return (featureProps = CollectionUtils.safeGetter(featureProps)); } public void addFeatureProp(FeatureProp featureProp) { featureProp.setFeature(this); getFeatureProps().add(featureProp); } public void removeFeatureProp(String cv, String term){ synchronized (featurePropsLock) { Iterator<FeatureProp> it = getFeatureProps().iterator(); while (it.hasNext()) { FeatureProp current = it.next(); if ((current.getType().getName().equals(term)) && (current.getType().getCv().getName()).equals(cv)) { logger.trace(String.format("Removing FeatureProp (ID %d) from feature '%s'", current.getFeaturePropId(), getUniqueName())); it.remove(); } } } } @Transient private Object featurePubsLock = new Object(); public Collection<FeaturePub> getFeaturePubs() { synchronized(featurePubsLock) { if (featurePubs == null) { featurePubs = new HashSet<FeaturePub>(); } return Collections.unmodifiableCollection(this.featurePubs); } } public void addFeaturePub(FeaturePub featurePub) { synchronized(featurePubsLock) { if (featurePubs == null) { featurePubs = new HashSet<FeaturePub>(); } this.featurePubs.add(featurePub); featurePub.setFeature(this); } } public FeaturePub addPub(Pub pub) { logger.trace(String.format("Adding pub '%s' to %s", pub.getUniqueName(), this.toString())); synchronized(featurePubsLock) { if (featurePubs == null) { featurePubs = new HashSet<FeaturePub>(); } FeaturePub featurePub = new FeaturePub(this, pub); this.featurePubs.add(featurePub); return featurePub; } } @Transient public Collection<Pub> getPubs() { synchronized(featurePubsLock) { if (featurePubs == null) { featurePubs = new HashSet<FeaturePub>(); } Collection<Pub> pubs = new HashSet<Pub>(); for(FeaturePub featurePub: this.featurePubs) { pubs.add(featurePub.getPub()); } return pubs; } } @Transient private Object featureSynonymsLock = new Object(); public Collection<FeatureSynonym> getFeatureSynonyms() { synchronized (featureSynonymsLock) { if (featureSynonyms == null) { featureSynonyms = new HashSet<FeatureSynonym>(); } return Collections.unmodifiableCollection(featureSynonyms); } } public void addFeatureSynonym(FeatureSynonym featureSynonym) { synchronized(featureSynonymsLock) { if (featureSynonyms == null) { featureSynonyms = new HashSet<FeatureSynonym>(); } this.featureSynonyms.add(featureSynonym); featureSynonym.setFeature(this); } } /** * Get the display name for the gene: preferably the name, otherwise the * display name * * @return the preferred display name, never null */ @Transient public String getDisplayName() { return (getName() != null) ? getName() : getUniqueName(); } @Transient public Collection<String> getPreviousSystematicIds() { Set<String> ret = new HashSet<String>(); for (FeatureSynonym featureSynonym: getFeatureSynonyms()) { Synonym synonym = featureSynonym.getSynonym(); if (("previous_systematic_id".equals(synonym.getType().getName())) && !featureSynonym.isCurrent()) { ret.add(synonym.getSynonymSGML()); } } return ret; } /** * Get all synonyms, of any type. * * @return a collection of synonym objects */ @Transient public Collection<Synonym> getSynonyms() { Collection<Synonym> ret = new HashSet<Synonym>(); for (FeatureSynonym featureSynonym: getFeatureSynonyms()) { ret.add(featureSynonym.getSynonym()); } return ret; } @Transient public Collection<Synonym> getSynonyms(String type) { Collection<Synonym> ret = new HashSet<Synonym>(); for (FeatureSynonym featureSynonym: getFeatureSynonyms()) { Synonym synonym = featureSynonym.getSynonym(); if (synonym.getType().getName().equals(type)) { ret.add(synonym); } } return ret; } @Transient public <T extends Feature> Collection<T> getRelatedFeatures(Class<T> featureClass, String relationshipTypeCvName, String relationshipTypeCvTerm) { Collection<T> relatedFeatures = new HashSet<T>(); for(FeatureRelationship featureRelationship: getFeatureRelationshipsForSubjectIdFilteredByCvNameAndTermName(relationshipTypeCvName, relationshipTypeCvTerm)) { Feature object = featureRelationship.getObjectFeature(); if (featureClass.isInstance(object)) { relatedFeatures.add(featureClass.cast(object)); } } return relatedFeatures; } public Collection<Phylonode> getPhylonodes() { return this.phylonodes; } private String calcMD5(String residue) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); StringBuilder hexValue = new StringBuilder(); for (byte b: md5.digest(residue.getBytes())) { hexValue.append(String.format("%02x", b)); } return hexValue.toString(); } catch (NoSuchAlgorithmException exp) { // Shouldn't happen - MD5 is a supported algorithm throw new RuntimeException("Could not find MD5 algorithm", exp); } } @Transient @Field(name = "synonym", index = Index.TOKENIZED, store = Store.YES) @Analyzer(impl = AllNamesAnalyzer.class) protected String getSynonymsAsSpaceSeparatedString() { List<String> synonyms = Lists.newArrayList(); for (Synonym synonym: getSynonyms()) { synonyms.add(synonym.getName()); } return allNamesSupport(synonyms); } /** * A string containing all the names by which this feature is known; * indexed by Lucene and used for searches. * * @return */ @Transient @Field(name = "allNames", index = Index.TOKENIZED, store = Store.YES) @Analyzer(impl = AllNamesAnalyzer.class) public String getAllNames() { List<String> names = generateNamesList(); /* * Commented by gv1. * * This method (commented out below) for getting the gene name using the colon (:) convention doesn't * work for recently loaded genes that follow a dot (.) convention for different transcripts. Extending * the same approach for dots won't work easily because dots are used throughout names, and relying on * naming conventions is an approach clearly fails when the conventions change. * * Instead, one should override the generateNamesList method to in Feature types that require * custom all names entries. This has been done in the <Polypeptide> and <Transcript> classes. * */ // if (sysId.indexOf(':') != -1) { // names.add(sysId.substring(0, sysId.lastIndexOf(':'))); // } StringBuilder ret = new StringBuilder(allNamesSupport(names)); ret.append(" "); ret.append(getSynonymsAsSpaceSeparatedString()); return ret.toString(); } /** * Override this to generate a custom names list for a feature. * * @return a list of names */ protected List<String> generateNamesList() { List<String> names = Lists.newArrayList(); if (getName() != null) { names.add(getName()); } String sysId = getUniqueName(); names.add(sysId); return names; } protected String allNamesSupport(List<String> names) { List<String> newNames = Lists.newArrayList(); if (!(this instanceof Gap)) { // these should keep their dashes for (String name : names) { if (name.contains("-")) { newNames.add(name.replaceAll("-", "")); } } names.addAll(newNames); } return StringUtils.collectionToDelimitedString(names, " "); } @Transient @Field(name = "start", store = Store.YES) @FieldBridge(impl = LocationBridge.class) public int getStart() { FeatureLoc loc = getRankZeroFeatureLoc(); if (loc == null) { return 0; } return loc.getFmin(); } @Transient @Field(name = "stop", store = Store.YES) @FieldBridge(impl = LocationBridge.class) public int getStop() { return getRankZeroFeatureLoc().getFmax(); } @Transient @Field(name = "strand", index=Index.UN_TOKENIZED, store = Store.YES) public int getStrand() { FeatureLoc loc = getRankZeroFeatureLoc(); if (loc == null) { return 0; } Short strand = loc.getStrand(); if (strand != null) { return strand; } return 0; } @Transient @Field(name = "chr", index=Index.UN_TOKENIZED, store = Store.YES) String getChr() { FeatureLoc loc = getRankZeroFeatureLoc(); if (loc == null) { return null; } return loc.getSourceFeature().getUniqueName(); } @Transient @Field(name = "chrId", index=Index.UN_TOKENIZED, store = Store.YES) int getChrId() { FeatureLoc loc = getRankZeroFeatureLoc(); if (loc == null) { return -1; } return loc.getSourceFeature().getFeatureId(); } @Transient @Field(name = "chrlen", store = Store.YES) int getChrLen() { FeatureLoc loc = getRankZeroFeatureLoc(); if (loc == null) { return 0; } return loc.getSourceFeature().getSeqLen(); } /** * Get the value of a feature property with the given CV name and term name. * Note that this method does not make it possible to distinguish between * a missing property and a property with a <code>null</code> value. See * {@link #hasProperty(String,String)}. * * @param cvName the name of the controlled vocabulary * @param termName the name of the term * @return the value of the property, or <code>null</code> if there is no such property */ @Transient public String getProperty(String cvName, String termName) { for (FeatureProp featureProp : this.getFeatureProps()) { CvTerm term = featureProp.getType(); if (term.getCv().getName().equals(cvName) && term.getName().equals(termName)) { return featureProp.getValue(); } } return null; } /** * Test whether this feature has a property with the given CV name and term name. * * @param cvName the name of the controlled vocabulary * @param termName the name of the term * @return <code>true</code> if there is such a property, or <code>false</code> if not */ @Transient public boolean hasProperty(String cvName, String termName) { for (FeatureProp featureProp : this.getFeatureProps()) { CvTerm term = featureProp.getType(); if (term.getCv().getName().equals(cvName) && term.getName().equals(termName)) { return true; } } return false; } /** * Add a FeatureRelationship of which this Feature is the object. * * @param subject the subject of the relationship * @param cvName the CV to which the relationship type belongs * @param termName the CV term denoting the relationship type * @return the newly-created FeatureRelationship object */ protected FeatureRelationship addFeatureRelationship(Feature subject, String cvName, String termName) { CvTerm type = cvDao.getCvTermByNameAndCvName(termName, cvName); if (type == null) { throw new RuntimeException(String.format("Failed to find term '%s' in cv '%s'", termName, cvName)); } logger.trace(String.format("Creating feature relationship (type '%s') from '%s' to '%s'", type, subject.getUniqueName(), this.getUniqueName())); FeatureRelationship relationship = new FeatureRelationship(subject, this, type, 0); if (this.featureRelationshipsForObjectId == null) { this.featureRelationshipsForObjectId = new HashSet<FeatureRelationship>(); } this.featureRelationshipsForObjectId.add(relationship); if (subject.featureRelationshipsForSubjectId == null) { subject.featureRelationshipsForSubjectId = new HashSet<FeatureRelationship>(); } subject.featureRelationshipsForSubjectId.add(relationship); return relationship; } public FeatureLoc addLocatedChild(Feature child, StrandedLocation location) { FeatureLoc loc = new FeatureLoc(this, child, location); synchronized (featureLocsForSrcFeatureIdLock) { if (this.featureLocsForSrcFeatureId == null) { this.featureLocsForSrcFeatureId = new HashSet<FeatureLoc>(); } this.featureLocsForSrcFeatureId.add(loc); } synchronized (child.featureLocsLock) { if (child.featureLocs == null) { child.featureLocs = new ArrayList<FeatureLoc>(); } child.featureLocs.add(loc); } return loc; } public FeatureLoc addLocatedChild(Feature child, int fmin, int fmax) { return addLocatedChild(child, fmin, fmax, 0, null); } public FeatureLoc addLocatedChild(Feature child, int fmin, int fmax, int strand, Integer phase) { return addLocatedChild(child, fmin, fmax, strand, phase, 0); } public FeatureLoc addLocatedChild(Feature child, int fmin, int fmax, int strand, Integer phase, int rank) { return addLocatedChild(child, fmin, fmax, strand, phase, 0, rank); } public FeatureLoc addLocatedChild(Feature child, int fmin, int fmax, int strand, Integer phase, int locgroup, int rank) { FeatureLoc loc = new FeatureLoc(this, child, fmin, fmax, strand, phase, locgroup, rank); System.err.println(String.format("Adding location for '%s' on '%s' %d-%d strand %d with phase=%s, locgroup=%d, rank=%d", child.getUniqueName(), this.getUniqueName(), fmin, fmax, strand, phase, locgroup, rank)); if (logger.isTraceEnabled()) { logger.trace(String.format("Adding location for '%s' on '%s' %d-%d strand %d with phase=%s, locgroup=%d, rank=%d", child.getUniqueName(), this.getUniqueName(), fmin, fmax, strand, phase, locgroup, rank)); } if (this.featureLocsForSrcFeatureId == null) { this.featureLocsForSrcFeatureId = new HashSet<FeatureLoc>(); } this.featureLocsForSrcFeatureId.add(loc); if (child.featureLocs == null) { child.featureLocs = new ArrayList<FeatureLoc>(); } child.featureLocs.add(loc); /* * The following is not currently done, and enabling it would cause loading problems * because the redundant locations are also added explicitly in various places. * * TODO decide whether or not we want these redundant locations automatically added here * // If we ourselves have featurelocs (e.g. we are a contig and have a supercontig), // give the child redundant locations to these. if (locgroup == 0) { for (FeatureLoc featureLoc: getFeatureLocs()) { logger.trace(String.format("Adding redundant featureloc (locgroup=%d) for '%s' on '%s' with rank %d", 1 + featureLoc.getLocGroup(), child.getUniqueName(), featureLoc.getSourceFeature().getUniqueName(), rank)); featureLoc.getSourceFeature().addLocatedChild(child, fmin + featureLoc.getFmin(), fmax + featureLoc.getFmin(), strand, phase, 1 + featureLoc.getLocGroup(), rank); } } */ return loc; } public FeatureProp addFeatureProp(String value, String cvName, String termName, int rank) { CvTerm type = cvDao.getCvTermByNameAndCvName(termName, cvName); if (type == null) { throw new RuntimeException(String.format("Failed to find term '%s' in cv '%s'", termName, cvName)); } FeatureProp fp = new FeatureProp(this, type, value, rank); if (featureProps == null) { featureProps = new HashSet<FeatureProp>(); } this.featureProps.add(fp); return fp; } public List<FeatureProp> getFeaturePropsFilteredByCvNameAndTermName(String cvName, String termName) { CvTerm type = cvDao.getCvTermByNameAndCvName(termName, cvName); if (type == null) { throw new RuntimeException(String.format("Failed to find term '%s' in cv '%s'", termName, cvName)); } List<FeatureProp> ret = new ArrayList<FeatureProp>(); for (FeatureProp featureProp : getFeatureProps()) { if (featureProp.getType().equals(type)) { ret.add(featureProp); } } if (ret.size() == 0) { return Collections.emptyList(); } return ret; } /** * Get the value of the feature property with the specified type. * If there is no such property, return <code>null</code>. If there is * more than one such property, throw a RuntimeException. * * @param cvName the name of the controlled vocabulary to which the property type belongs * @param termName the property type (within the specified vocabulary) * @return the value of the feature property with the specified type, * or <code>null</code> if there is no such property. */ public String getFeatureProp(String cvName, String termName) { List<FeatureProp> props = getFeaturePropsFilteredByCvNameAndTermName(cvName, termName); if (props.isEmpty()) { return null; } if (props.size() > 1) { throw new RuntimeException(String.format("Feature '%s' has more than one '%s:%s' property", getUniqueName(), cvName, termName)); } return props.get(0).getValue(); } public FeatureCvTerm addCvTerm(String cvName, String cvTermName) { return addCvTerm(cvName, cvTermName, null); } public FeatureCvTerm addCvTerm(String cvName, String cvTermName, String dbName) { return addCvTerm(cvName, cvTermName, dbName, true); } public FeatureCvTerm addCvTerm(String cvName, String cvTermName, boolean createIfNotFound) { return addCvTerm(cvName, cvTermName, null, createIfNotFound); } public FeatureCvTerm addCvTerm(String cvName, String cvTermName, String dbName, boolean createIfNotFound) { if (createIfNotFound) { return addCvTerm(cvDao.findOrCreateCvTermByNameAndCvName(cvTermName, cvName, dbName)); } else { CvTerm cvTerm = cvDao.getCvTermByNameAndCvName(cvTermName, cvName); if (cvTerm == null) { return null; } return addCvTerm(cvTerm); } } public FeatureCvTerm addCvTerm(CvTerm cvTerm) { FeatureCvTerm featureCvTerm = new FeatureCvTerm(cvTerm, this, nullPub(), false, 0); addFeatureCvTerm(featureCvTerm); return featureCvTerm; } @Transient public Feature getPrimarySourceFeature() { FeatureLoc featureLoc = this.getRankZeroFeatureLoc(); if (featureLoc == null) { return null; } return featureLoc.getSourceFeature(); } @Transient public Iterable<Feature> getSourceFeatures() { List<Feature> sourceFeatures = new ArrayList<Feature>(); for (FeatureLoc featureLoc: this.getFeatureLocs()) { Feature sourceFeature = featureLoc.getSourceFeature(); if (sourceFeature == null) { logger.warn(String.format("Feature '%s' has a location (FeatureLoc ID %d) with no source feature", this.getUniqueName(), featureLoc.getFeatureLocId())); } else { sourceFeatures.add(sourceFeature); } } return sourceFeatures; } public FeatureSynonym addSynonym(String synonymString) { return addSynonym("synonym", synonymString); } /* * Avoid the overhead of transaction-creation and synchronisation * where possible. This requires (build-time or load-time) AspectJ * weaving, because we're calling a transactional method from * within this object. */ protected Pub nullPub() { Session session = SessionFactoryUtils.getSession(sessionFactory, false); return (Pub) session.load(Pub.class, 1); } protected FeatureSynonym addSynonym(String synonymType, String synonymString) { logger.trace(String.format("Adding synonym '%s' of type '%s' to feature '%s'", synonymString, synonymType, getUniqueName())); return addSynonym(generalDao.getOrCreateSynonym(synonymType, synonymString)); } public FeatureSynonym addSynonym(Synonym synonym) { return addSynonym(synonym, true, false); } public FeatureSynonym addSynonym(Synonym synonym, boolean isCurrent, boolean isInternal) { Pub nullPub = nullPub(); FeatureSynonym featureSynonym = new FeatureSynonym(synonym, this, nullPub , isCurrent, isInternal); this.addFeatureSynonym(featureSynonym); return featureSynonym; } /** * Move the lower bound of this feature leftwards. The new location is * specified relative to the primary source feature, but all <code>FeatureLoc</code>s * will be updated by the same relative amount. * * @param fmin the new <code>fmin</code>, relative to the primary location */ public void lowerFminTo(int fmin) { FeatureLoc primaryLoc = getRankZeroFeatureLoc(); logger.trace(String.format("Feature '%s' starts at %s:%d; lowering to %d", getUniqueName(), primaryLoc.getSourceFeature().getUniqueName(), primaryLoc.getFmin(), fmin)); int lowerByBases = primaryLoc.getFmin() - fmin; if (lowerByBases <= 0) { logger.trace("Nothing to be done for lowerFminTo"); return; } Iterator<FeatureLoc> it = featureLocs.iterator(); while (it.hasNext()) { FeatureLoc featureLoc = it.next(); int newFmin = featureLoc.getFmin() - lowerByBases; if (newFmin < 0) { if (featureLoc.getRank() == 0) { throw new RuntimeException(String.format("New fmin (%d) of feature '%s' relative to '%s' is before start", newFmin, getUniqueName(), featureLoc.getSourceFeature().getUniqueName())); } logger.warn(String.format("Removing secondary location of '%s' on '%s' because new fmin would be %d", getUniqueName(), featureLoc.getSourceFeature().getUniqueName(), newFmin)); it.remove(); featureLoc.setSourceFeature(null); } else { featureLoc.setFmin(newFmin); } } } /** * Move the upper bound of this feature rightwards. The new location is * specified relative to the primary source feature, but all <code>FeatureLoc</code>s * will be updated by the same relative amount. * * @param fmax the new <code>fmax</code>, relative to the primary location */ public void raiseFmaxTo(int fmax) { FeatureLoc primaryLoc = getRankZeroFeatureLoc(); logger.trace(String.format("Feature '%s' stops at %s:%d; raising to %d", getUniqueName(), primaryLoc.getSourceFeature().getUniqueName(), primaryLoc.getFmax(), fmax)); int raiseByBases = fmax - primaryLoc.getFmax(); if (raiseByBases <= 0) { logger.trace("Nothing to be done for raiseFmaxTo"); return; } Iterator<FeatureLoc> it = featureLocs.iterator(); while (it.hasNext()) { FeatureLoc featureLoc = it.next(); int newFmax = featureLoc.getFmax() + raiseByBases; if (newFmax > featureLoc.getSourceFeature().getSeqLen()) { if (featureLoc.getRank() == 0) { throw new RuntimeException(String.format("New fmax (%d) of feature '%s' relative to '%s' is after end (%d)", newFmax, getUniqueName(), featureLoc.getSourceFeature().getUniqueName(), featureLoc.getSourceFeature().getSeqLen())); } logger.warn(String.format("Removing secondary location of '%s' on '%s' because new fmax would be %d (source seqlen = %d)", getUniqueName(), featureLoc.getSourceFeature().getUniqueName(), newFmax, featureLoc.getSourceFeature().getSeqLen())); it.remove(); featureLoc.setSourceFeature(null); } else { featureLoc.setFmax(newFmax); } generalDao.saveOrUpdate(featureLoc); } } public List<FeatureCvTerm> getFeatureCvTermsFilteredByCvNameStartsWith(String prefix) { List<FeatureCvTerm> ret = new ArrayList<FeatureCvTerm>(); for (FeatureCvTerm featureCvTerm : this.getFeatureCvTerms()) { if (featureCvTerm.getCvTerm().getCv().getName().startsWith(prefix)) { ret.add(featureCvTerm); } } return ret; } public List<FeatureRelationship> getFeatureRelationshipsForSubjectIdFilteredByCvNameAndTermName( String cvName, String termName) { List<FeatureRelationship> ret = new ArrayList<FeatureRelationship>(); CvTerm type = cvDao.getCvTermByNameAndCvName(termName, cvName); if (type == null) { throw new RuntimeException(String.format("Failed to find term '%s' in cv '%s'", termName, cvName)); } Collection<FeatureRelationship> featureRels = getFeatureRelationshipsForSubjectId(); for (FeatureRelationship featureRel : featureRels) { if (featureRel.getType().equals(type)) { ret.add(featureRel); } } if (ret.size() == 0) { return Collections.emptyList(); } return ret; } public List<FeatureCvTerm> getFeatureCvTermsFilteredByCvNameAndCvTermName(String cvName, String termName) { CvTerm type = cvDao.getCvTermByNameAndCvName(termName, cvName); if (type == null) { throw new RuntimeException(String.format("Failed to find term '%s' in cv '%s'", termName, cvName)); } List<FeatureCvTerm> ret = new ArrayList<FeatureCvTerm>(); for (FeatureCvTerm featureCvTerm : this.getFeatureCvTerms()) { if (featureCvTerm.getCvTerm().equals(type)) { ret.add(featureCvTerm); } } return ret; } @Transactional public void addSimilarity(SimilarityI similarity) { logger.trace(String.format("Adding similarity '%s' to feature '%s' (ID=%d)", similarity, getUniqueName(), getFeatureId())); Session session = SessionFactoryUtils.getSession(sessionFactory, false); Analysis analysis = similarity.getAnalysis(); if (analysis == null) { analysis = new Analysis(); analysis.setProgram(similarity.getAnalysisProgram()); analysis.setProgramVersion(similarity.getAnalysisProgramVersion()); session.persist(analysis); } String matchUniqueName = String.format("MATCH_%s", similarity.getUniqueIdentifier()); ProteinMatch match = new ProteinMatch(getOrganism(), matchUniqueName); session.persist(match); AnalysisFeature analysisFeature = new AnalysisFeature(analysis, match); analysisFeature.setRawScore(similarity.getRawScore()); analysisFeature.setSignificance(similarity.getEValue()); analysisFeature.setIdentity(similarity.getId()); session.persist(analysisFeature); match.addFeatureProp(String.format("%.02g", similarity.getUngappedId()), "genedb_misc", "ungapped id", 0); match.addFeatureProp(similarity.getOverlap() + " aa overlap", "genedb_misc", "overlap", 0); String targetUniqueName = String.format("%s_%s", organism.getCommonName(), similarity.getUniqueIdentifier()); Region target = new Region(getOrganism(), targetUniqueName, true, false, new Timestamp(System.currentTimeMillis())); target.setSeqLen(similarity.getLength()); target.setDbXRef(similarity.getPrimaryDbXRef()); for (DbXRef dbXRef: similarity.getSecondaryDbXRefs()) { target.addDbXRef(dbXRef); } session.persist(target); target.addFeatureProp(similarity.getOrganismName(), "feature_property", "organism", 0); target.addFeatureProp(similarity.getGeneName(), "sequence", "gene", 2); target.addFeatureProp(similarity.getProduct(), "genedb_misc", "product", 1); this.addLocatedChild (match, similarity.getQueryStart() - 1, similarity.getQueryEnd(), 0 /*strand*/, null /*phase*/, 0 /*locgroup */, 0 /*rank*/); target.addLocatedChild(match, similarity.getTargetStart() - 1, similarity.getTargetEnd(), 0 /*strand*/, null /*phase*/, 0 /*locgroup */, 1 /*rank*/); } /** * Delete this feature from the database. * This method is overridden in subclasses to automatically remove * dependent features too. */ public void delete() { logger.trace(String.format("Deleting feature '%s'", getUniqueName())); cvDao.delete(this); } @Transient private Object analysisFeaturesLock = new Object(); public Collection<AnalysisFeature> getAnalysisFeatures() { synchronized(analysisFeaturesLock) { if (analysisFeatures == null) { analysisFeatures = new HashSet<AnalysisFeature>(); } return Collections.unmodifiableCollection(this.analysisFeatures); } } /** * Get the AnalysisFeature object associated with this feature, assuming there is at most one. * @return the associated AnalysisFeature object, or <code>null</code> if there isn't one. * @throws RuntimeException if there is more than one AnalysisFeature associated with this feature */ @Transient public AnalysisFeature getAnalysisFeature() { synchronized(analysisFeaturesLock) { Collection<AnalysisFeature> analysisFeatures = getAnalysisFeatures(); if (analysisFeatures == null || analysisFeatures.isEmpty()) { return null; } if (analysisFeatures.size() > 1) { throw new RuntimeException(String.format("Feature '%s' (ID=%d) has more than one AnalysisFeature", getUniqueName(), getFeatureId())); } return analysisFeatures.iterator().next(); } } /** * Create an AnalysisFeature that associates this feature with the specified * analysis. * @param analysis the analysis. May not be null. * @return the newly-created AnalysisFeatuer * @throws NullPointerException if the suppled analysis is null */ public AnalysisFeature createAnalysisFeature(Analysis analysis) { return this.createAnalysisFeature(analysis, null, null); } /** * Create an AnalysisFeature that associates this feature with the specified * analysis. * @param analysis the analysis. May not be null. * @param score score generated by the analysis as a String. May be null. * @param evalue evalue generated by the analysis as a String. May be null. * @return the newly-created AnalysisFeature * @throws NullPointerException if the suppled analysis is null */ public AnalysisFeature createAnalysisFeature(Analysis analysis, String score, String evalue) { if (analysis == null) { throw new NullPointerException("Analysis is null in createAnalysisFeature"); } logger.trace(String.format("Adding AnalysisFeature to '%s' (ID=%d)", getUniqueName(), getFeatureId())); synchronized(analysisFeaturesLock) { AnalysisFeature analysisFeature = new AnalysisFeature(analysis, this); if (analysisFeatures == null) { analysisFeatures = new HashSet<AnalysisFeature>(); } if (score != null) { double scoreDouble = Double.valueOf(score.trim()).doubleValue(); analysisFeature.setRawScore(scoreDouble); } if (evalue != null) { double evalueDouble = Double.valueOf(evalue.trim()).doubleValue(); analysisFeature.setSignificance(evalueDouble); } analysisFeatures.add(analysisFeature); return analysisFeature; } } @Transient @Analyzer(impl = AllNamesAnalyzer.class) @Field(name = "dbxref", index = Index.TOKENIZED, store = Store.YES) public String getDbxrefsAsSpaceSeparatedString() { List<String> dbxrefs = new ArrayList<String>(); if (this.dbXRef != null) addDbxref(dbxrefs, this.dbXRef); for (FeatureDbXRef fdbx : this.getFeatureDbXRefs()) { addDbxref(dbxrefs, fdbx.getDbXRef()); } for (FeaturePub fp : this.getFeaturePubs()) { for (PubDbXRef pd : fp.getPub().getPubDbXRefs()) { addDbxref(dbxrefs, pd.getDbXRef()); } } for (FeatureCvTerm fct : this.getFeatureCvTerms()) { for (FeatureCvTermPub fctp : fct.getFeatureCvTermPubs()) { for (PubDbXRef pd : fctp.getPub().getPubDbXRefs()) { addDbxref(dbxrefs, pd.getDbXRef()); } } } return StringUtils.collectionToDelimitedString(dbxrefs, " "); } @Transient private void addDbxref(List<String> dbxrefs, DbXRef dbxref ) { if (dbxref == null) { return; } if (dbxref.getAccession() == null) { return; } if (dbxref.getDb() == null) { return; } if (dbxref.getDb().getName() == null) { return; } dbxrefs.add(dbxref.getDb().getName()+ ":" +dbxref.getAccession()); dbxrefs.add(dbxref.getAccession()); } @Override public String toString() { return String.format("feature '%s' (ID=%d)", getUniqueName(), getFeatureId()); } /* The hashCode() and equals() methods were generated by Eclipse * using the fields uniqueName, type, and organism, which constitute * a unique key in the database table. */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((organism == null) ? 0 : organism.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); result = prime * result + ((uniqueName == null) ? 0 : uniqueName.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final Feature other = (Feature) obj; if (organism == null) { if (other.organism != null) return false; } else if (!organism.equals(other.organism)) return false; if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; if (uniqueName == null) { if (other.uniqueName != null) return false; } else if (!uniqueName.equals(other.uniqueName)) return false; return true; } }