package org.genedb.web.mvc.model; import org.genedb.db.audit.ChangeSet; import org.gmod.schema.feature.AbstractGene; import org.gmod.schema.feature.Gap; import org.gmod.schema.feature.Gene; import org.gmod.schema.feature.MRNA; import org.gmod.schema.feature.NcRNA; import org.gmod.schema.feature.Polypeptide; import org.gmod.schema.feature.Pseudogene; import org.gmod.schema.feature.PseudogenicTranscript; import org.gmod.schema.feature.RRNA; import org.gmod.schema.feature.SnRNA; import org.gmod.schema.feature.TRNA; import org.gmod.schema.feature.Transcript; import org.gmod.schema.mapped.Feature; import org.apache.log4j.Logger; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.TermQuery; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.search.FullTextSession; import org.hibernate.search.Search; import org.hibernate.search.SearchFactory; import org.hibernate.search.reader.ReaderProvider; import org.hibernate.search.store.DirectoryProvider; import org.springframework.orm.hibernate3.SessionFactoryUtils; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.util.HashSet; import java.util.Set; import com.google.common.collect.Sets; @Repository @Transactional public class IndexSynchroniser implements IndexUpdater{ @Override public int updateTranscriptCache(ChangeSet changeSet) throws Exception { // TODO Auto-generated method stub return 0; } private static final Logger logger = Logger.getLogger(IndexSynchroniser.class); private static final int BATCH_SIZE = 10;//must match BATCH_SIZE in config private SessionFactory sessionFactory; @Transactional public boolean updateAllCaches(ChangeSet changeSet) { logger.debug("Starting updateAllCaches"); Session session = SessionFactoryUtils.getSession(sessionFactory, false); FullTextSession fullTextSession = Search.createFullTextSession(session); //Delete deleted features Set failedDeletes = deleteFeatures(fullTextSession, changeSet); //prevent unncecesary flush fullTextSession.setFlushMode(FlushMode.MANUAL); //disable 2nd-level cache ops fullTextSession.setCacheMode(CacheMode.IGNORE); //Index altered features indexFeatures(fullTextSession, changeSet); return true; } /** * Index features in batches * @param session * @param changeSet */ private void indexFeatures(FullTextSession session, ChangeSet changeSet){ logger.debug("Starting indexFeatures"); Set<Integer> alteredIds = Sets.newHashSet(); alteredIds.addAll(changeSet.newFeatureIds(AbstractGene.class)); alteredIds.addAll(changeSet.changedFeatureIds(AbstractGene.class)); alteredIds.addAll(changeSet.newFeatureIds(Transcript.class)); alteredIds.addAll(changeSet.changedFeatureIds(Transcript.class)); alteredIds.addAll(changeSet.newFeatureIds(Polypeptide.class)); alteredIds.addAll(changeSet.changedFeatureIds(Polypeptide.class)); alteredIds.addAll(changeSet.newFeatureIds(Gap.class)); alteredIds.addAll(changeSet.changedFeatureIds(Gap.class)); Set<Integer> failedIds = Sets.newHashSet(); Set<Integer> batchIds = Sets.newHashSet(); Set<Integer> unflushedIds = Sets.newHashSet(); int index = 0; //First attempt to index for (Integer featureId : alteredIds) { index++; try{ logger.debug("featureID " + featureId + " being loaded"); batchIds.add(featureId); Feature feature = (Feature)session.get(Feature.class, featureId); session.index(feature); logger.debug("--featureID: " + featureId + " indexed..."); }catch(Exception e){ logger.error(String.format("Error found in first attempt with feature ID: %s", featureId), e); index = 0; failedIds.add(featureId); unflushedIds.addAll(batchIds); session.clear(); } if (index % BATCH_SIZE == 0){ batchIds.clear(); session.clear(); } } //Second attempt to index non-defective features index=0; for(Integer featureId : unflushedIds){ if(!failedIds.contains(featureId)){ index++; Feature feature = (Feature)session.get(Feature.class, featureId); logger.debug(String.format("About to try and index unflushed id %s", featureId)); session.index(feature); logger.debug(String.format("Indexed unflushed id %s", featureId)); } if (index % BATCH_SIZE == 0){ batchIds.clear(); // TODO needed ? session.clear(); } } logger.debug("Exiting indexFeatures"); } /** * Delete Features * @param session * @param changeSet * @return */ private Set<Integer> deleteFeatures(FullTextSession session, ChangeSet changeSet) { logger.debug("Starting deleteFeatures"); Set<Integer> failedDeletes = new HashSet<Integer>(); Set<Integer> deletedIds = Sets.newHashSet(); deletedIds.addAll(changeSet.deletedFeatureIds(Gene.class)); deletedIds.addAll(changeSet.deletedFeatureIds(Transcript.class)); deletedIds.addAll(changeSet.deletedFeatureIds(Polypeptide.class)); deletedIds.addAll(changeSet.deletedFeatureIds(Gap.class)); IndexSearcher indexSearcher = createIndexSearcher(session); try { for (Integer featureId : deletedIds) { try { deleteFeature(session, indexSearcher, featureId); } catch (Exception exp) { logger.error(String.format("Failed to delete %s", featureId), exp); failedDeletes.add(featureId); } } if (failedDeletes.size()>0) { logger.debug(String.format("Ended deleteFeatures with %s features undeleted due to failures", failedDeletes.size())); } else { logger.debug("Ended deleteFeatures with no errors"); } } finally { try { indexSearcher.close(); } catch (IOException io) { logger.error("Failed to close the Index Searcher", io); } } return failedDeletes; } /** * Delete a feature * @param session * @param indexSearcher * @param featureId * @throws Exception */ private void deleteFeature(FullTextSession session, IndexSearcher indexSearcher, Integer featureId)throws Exception{ logger.debug(String.format("Starting deleteFeature(session, indexSearcher, %s)", featureId)); Class<? extends Feature> entityType = findFeatureSubclass(indexSearcher, featureId); session.purge(entityType, featureId); logger.debug(String.format("Deleted ID %s of entity type %s", featureId, entityType.getName())); } /** * Create the IndexSearcher * @param session * @return */ @SuppressWarnings("unchecked") private IndexSearcher createIndexSearcher(FullTextSession session){ logger.debug("Starting createIndexSearcher"); SearchFactory searchFactory = session.getSearchFactory(); ReaderProvider readerProvider = session.getSearchFactory().getReaderProvider(); DirectoryProvider[] directoryProviders = searchFactory.getDirectoryProviders(Feature.class); IndexReader indexReader = readerProvider.openReader(directoryProviders[0]); IndexSearcher indexSearcher = new IndexSearcher(indexReader); logger.debug("Ending createIndexSearcher"); return indexSearcher; } /** * * @param indexSearcher * @param featureId * @return * @throws Exception */ @SuppressWarnings("unchecked") private Class<? extends Feature> findFeatureSubclass(IndexSearcher indexSearcher, Integer featureId)throws Exception{ TermQuery query = new TermQuery(new Term("featureId", Integer.toString(featureId))); Hits hits = indexSearcher.search(query); Document doc = hits.doc(0); Field field = doc.getField("_hibernate_class"); String className = field.stringValue(); logger.debug(String.format("Class name: %s for ID %s", className, featureId)); return (Class<? extends Feature>) Class.forName(className); } /* * This is useful for JUnit testing */ @Transactional public void purgeAll(){ logger.debug("Starting purgeAll"); Session session = SessionFactoryUtils.getSession(sessionFactory, false); FullTextSession fullTextSession = Search.createFullTextSession(session); fullTextSession.purgeAll(Gap.class); fullTextSession.purgeAll(Gene.class); fullTextSession.purgeAll(Transcript.class); fullTextSession.purgeAll(MRNA.class); fullTextSession.purgeAll(NcRNA.class); fullTextSession.purgeAll(TRNA.class); fullTextSession.purgeAll(RRNA.class); fullTextSession.purgeAll(SnRNA.class); fullTextSession.purgeAll(Polypeptide.class); fullTextSession.purgeAll(Pseudogene.class); fullTextSession.purgeAll(PseudogenicTranscript.class); fullTextSession.getSearchFactory().optimize(); logger.debug("Ended purgeAll"); } /* * This is useful for JUnit testing */ @Transactional public void indexSingle(Integer featureId){ logger.debug("Starting indexSingle"); Session session = SessionFactoryUtils.getSession(sessionFactory, false); FullTextSession fullTextSession = Search.createFullTextSession(session); Feature feature = (Feature)fullTextSession.get(Feature.class, featureId); fullTextSession.index(feature); logger.debug("Ended indexSingle"); } public SessionFactory getSessionFactory() { return sessionFactory; } public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } }