package org.gmod.schema.utils; import org.genedb.db.dao.CvDao; import org.genedb.db.dao.GeneralDao; import org.genedb.db.dao.PubDao; import org.genedb.util.TwoKeyMap; import org.gmod.schema.mapped.CvTerm; import org.gmod.schema.mapped.Db; import org.gmod.schema.mapped.DbXRef; import org.gmod.schema.mapped.Pub; import org.gmod.schema.mapped.Synonym; import org.apache.log4j.Logger; import org.hibernate.EmptyInterceptor; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Maintains a cache of objects that have been created but * not yet flushed, so we can look them up even though they're * not in the database yet. If you register your instance * of this class as a Hibernate interceptor, the cache is cleared * whenever Hibernate flushes. * <p> * This mechanism is available to {@link org.genedb.db.loading.auxiliary.Loader} subclasses via * the protected Loader field <code>objectManager</code>. At the time of * writing, it's only used by {@link org.genedb.db.loading.auxiliary.InterProLoader}. * * This class is not currently concurrency-safe. * * @author rh11 */ public class ObjectManager extends EmptyInterceptor { private static final Logger logger = Logger.getLogger(ObjectManager.class); private GeneralDao generalDao; private PubDao pubDao; private CvDao cvDao; private TwoKeyMap<String,String,DbXRef> dbxrefsByAccByDb = new TwoKeyMap<String,String,DbXRef>(); private TwoKeyMap<String,String,Synonym> synonymsByTypeAndName = new TwoKeyMap<String,String,Synonym>(); private Map<String,Pub> pubsByUniqueName = new HashMap<String, Pub>(); private Map<String,Db> dbsByUppercaseName = new HashMap<String,Db>(); @Override @SuppressWarnings("unchecked") public void postFlush(Iterator entities) { logger.debug("Flushing dbxrefs, synonyms and pubs"); dbxrefsByAccByDb.clear(); synonymsByTypeAndName.clear(); pubsByUniqueName.clear(); dbsByUppercaseName.clear(); } /** * Flush the cache. */ public void flush() { postFlush(null); } /** * Get or create a DbXRef. Works even if the DbXRef has been * created but not yet flushed, as long as it was created using * this object. * * @param identifier A string of the form <code>db:accession</code> * @return the existing or newly-created DbXRef */ public DbXRef getDbXRef(String identifier) { int colonIndex = identifier.indexOf(':'); if (colonIndex == -1) throw new IllegalArgumentException(String.format( "Failed to parse dbxref identifier '%s'", identifier)); return getDbXRef(identifier.substring(0, colonIndex), identifier.substring(colonIndex + 1)); } /** * Get or create a DbXRef. Works even if the DbXRef has been * created but not yet flushed, as long as it was created using * this object. * * @param dbName The database name * @param accession The database-specific identifier * @return the existing or newly-created DbXRef, * or <code>null</code> if the named database does not exist. */ public DbXRef getDbXRef(String dbName, String accession) { if (dbName.equals("SWALL")) { dbName = "UniProt"; } logger.debug(String.format("Getting DbXRef '%s'/'%s'", dbName, accession)); if (dbxrefsByAccByDb.containsKey(dbName, accession)) { return dbxrefsByAccByDb.get(dbName, accession); } DbXRef dbxref = findOrCreateDbXRefFromDbAndAccession(dbName, accession); dbxrefsByAccByDb.put(dbName, accession, dbxref); return dbxref; } /** * Get or create a DbXRef, and set the description if it's not already set. * Works even if the DbXRef has been created but not yet flushed, as long * as it was created using this object. * * @param dbName The database name * @param accession The database-specific identifier * @param description The description to use * @return the existing or newly-created DbXRef, * or <code>null</code> if the named database does not exist. */ public DbXRef getDbXRef(String dbName, String accession, String description) { DbXRef dbxref = getDbXRef(dbName, accession); if (dbxref != null && dbxref.getDescription() == null) { dbxref.setDescription(description); } return dbxref; } public Db getExistingDbByName(String dbName) { String uppercaseDbName = dbName.toUpperCase(); synchronized (dbsByUppercaseName) { if (dbsByUppercaseName.containsKey(uppercaseDbName)) { return dbsByUppercaseName.get(uppercaseDbName); } } Db db = generalDao.getDbByName(uppercaseDbName); dbsByUppercaseName.put(uppercaseDbName, db); return db; } private TwoKeyMap<String,String,Integer> cvTermIdsByCvAndName = new TwoKeyMap<String,String,Integer>(); /** * Get the ID number of an already-existing CvTerm, caching the results. * This cache is never flushed. * * @param cvName the name of the CV * @param cvTermName the term name * @return the ID of the corresponding CvTerm object */ public int getIdOfExistingCvTerm(String cvName, String cvTermName) { logger.trace(String.format("Looking for cvterm '%s:%s'", cvName, cvTermName)); if (cvTermIdsByCvAndName.containsKey(cvName, cvTermName)) { logger.trace("Found cvterm locally"); return cvTermIdsByCvAndName.get(cvName, cvTermName); } logger.trace("CV term not found locally. Falling back to CvDao"); CvTerm cvTerm = cvDao.getCvTermByNameAndCvName(cvTermName, cvName); if (cvTerm == null) { throw new IllegalArgumentException(String.format("CV term '%s:%s' not found in database", cvName, cvTermName)); } int cvTermId = cvTerm.getCvTermId(); cvTermIdsByCvAndName.put(cvName, cvTermName, cvTermId); return cvTermId; } /** * Get or create a synonym. * * @param synonymType * @param synonymString * @return */ public Synonym getSynonym(String synonymTypeName, String synonymString) { logger.trace(String.format("Looking for synonym '%s' of type '%s'", synonymString, synonymTypeName)); if (synonymsByTypeAndName.containsKey(synonymTypeName, synonymString)) { logger.trace("Found synonym locally"); return synonymsByTypeAndName.get(synonymTypeName, synonymString); } logger.trace("Synonym not found locally. Falling back to GeneralDao"); int synonymTypeId = getIdOfExistingCvTerm("genedb_synonym_type", synonymTypeName); Synonym synonym = generalDao.getOrCreateSynonym(synonymTypeId, synonymString); synonymsByTypeAndName.put(synonymTypeName, synonymString, synonym); return synonym; } /** * Find or create a DbXRef given an database name and accession identifier. * If the DbXRef is created, it will be persisted before returning. * * @param dbName the name of the database, which must exist * @param accession the accession identifier * @return the persistent DbXRef that was found or created, * or <code>null</code> if the database does not exist. */ private DbXRef findOrCreateDbXRefFromDbAndAccession(String dbName, String accession) { Db db = getExistingDbByName(dbName); if (db == null) { return null; } DbXRef dbXRef = generalDao.getDbXRefByDbAndAcc(db, accession); if (dbXRef == null) { logger.debug(String.format("Creating new dbxref '%s:%s'", dbName, accession)); dbXRef = new DbXRef(db, accession); generalDao.persist(dbXRef); } return dbXRef; } /** * Find or create a publication object. * * @param uniqueName the unique name of the publication * @param type the type of publication. This should be a term in the <code>genedb_literature</code> CV * @return the Pub object that was found or created */ public Pub getPub(String uniqueName, String type) { logger.trace(String.format("Looking for Pub with uniqueName '%s'", uniqueName)); if (pubsByUniqueName.containsKey(uniqueName)) { return pubsByUniqueName.get(uniqueName); } CvTerm typeCvTerm = cvDao.getCvTermByNameAndCvName(type, "genedb_literature"); if (typeCvTerm == null) { throw new IllegalArgumentException(String.format( "The term '%s' does not exist in the CV 'genedb_literature'", type)); } Pub pub = pubDao.getPubByUniqueName(uniqueName); if (pub == null) { pub = new Pub(uniqueName, typeCvTerm); pubDao.persist(pub); } pubsByUniqueName.put(uniqueName, pub); return pub; } public void setGeneralDao(GeneralDao generalDao) { this.generalDao = generalDao; } public void setPubDao(PubDao pubDao) { this.pubDao = pubDao; } public void setCvDao(CvDao cvDao) { this.cvDao = cvDao; } public void setDaos(GeneralDao generalDao, PubDao pubDao, CvDao cvDao) { this.generalDao = generalDao; this.pubDao = pubDao; this.cvDao = cvDao; } }