Package org.rssowl.core.internal.persist.search

Source Code of org.rssowl.core.internal.persist.search.ModelSearchImpl$SimpleHitCollector

/*   **********************************************************************  **
**   Copyright notice                                                       **
**                                                                          **
**   (c) 2005-2009 RSSOwl Development Team                                  **
**   http://www.rssowl.org/                                                 **
**                                                                          **
**   All rights reserved                                                    **
**                                                                          **
**   This program and the accompanying materials are made available under   **
**   the terms of the Eclipse Public License v1.0 which accompanies this    **
**   distribution, and is available at:                                     **
**   http://www.rssowl.org/legal/epl-v10.html                               **
**                                                                          **
**   A copy is found in the file epl-v10.html and important notices to the  **
**   license from the team is found in the textfile LICENSE.txt distributed **
**   in this package.                                                       **
**                                                                          **
**   This copyright notice MUST APPEAR in all copies of the file!           **
**                                                                          **
**   Contributors:                                                          **
**     RSSOwl Development Team - initial API and implementation             **
**                                                                          **
**  **********************************************************************  */

package org.rssowl.core.internal.persist.search;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.NumberTools;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexFileNameFilter;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BooleanQuery.TooManyClauses;
import org.apache.lucene.search.HitCollector;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.LockFactory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.NativeFSLockFactory;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.rssowl.core.internal.Activator;
import org.rssowl.core.internal.InternalOwl;
import org.rssowl.core.internal.persist.service.DBManager;
import org.rssowl.core.persist.IGuid;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.ISearch;
import org.rssowl.core.persist.ISearchCondition;
import org.rssowl.core.persist.dao.INewsDAO;
import org.rssowl.core.persist.reference.NewsReference;
import org.rssowl.core.persist.service.IModelSearch;
import org.rssowl.core.persist.service.IndexListener;
import org.rssowl.core.persist.service.PersistenceException;
import org.rssowl.core.persist.service.ProfileLockedException;
import org.rssowl.core.util.SearchHit;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

/**
* The central interface for searching types from the persistence layer. The
* implementation is contributable via extension-point mechanism.
*
* @author ijuma
* @author bpasero
*/
public class ModelSearchImpl implements IModelSearch {

  /* Cached News States */
  private static final INews.State[] NEWS_STATES = INews.State.values();

  /* Number of news to resolve for indexing at once */
  private static final int INDEX_CHUNK_SIZE = 500;

  /* An increased clauses count to set in case of a MaxClouseCountException */
  static final int MAX_CLAUSE_COUNT = 65536;

  private volatile IndexSearcher fSearcher;
  private volatile Indexer fIndexer;
  private volatile Directory fDirectory;
  private final List<IndexListener> fIndexListeners = new CopyOnWriteArrayList<IndexListener>();
  private final Map<IndexSearcher, AtomicInteger> fSearchers = new ConcurrentHashMap<IndexSearcher, AtomicInteger>(3, 0.75f, 1);

  /*
   * @see org.rssowl.core.model.search.IModelSearch#startup()
   */
  public void startup() throws PersistenceException {
    startup(false);
  }

  private void startup(boolean clearIndex) throws PersistenceException {
    try {
      if (fDirectory == null) {
        String path = Activator.getDefault().getStateLocation().toOSString();

        /*
         * Delete Lucene Files if clearIndex == true. While Lucene is actually
         * capable of recreating the index without deleting files, we have seen
         * IOExceptions while Lucene was trying to recreate the index. Making sure
         * the index files are deleted will prevent these situations from occuring.
         */
        if (clearIndex) {
          File directory = new File(path);
          File[] indexFiles = directory.listFiles(new IndexFileNameFilter());
          try {
            for (File file : indexFiles) {
              file.delete();
            }
          } catch (Exception e) {
            Activator.getDefault().logError(e.getMessage(), e);
          }
        }

        /* Create Directory */
        LockFactory lockFactory = new NativeFSLockFactory(path);
        fDirectory = FSDirectory.getDirectory(path, lockFactory);
      }

      if (fIndexer == null)
        fIndexer = new Indexer(this, fDirectory);

      fIndexer.initIfNecessary(clearIndex);

      synchronized (this) {
        if (fSearcher == null)
          fSearcher = createIndexSearcher();
      }
    } catch (LockObtainFailedException e) {
      throw new ProfileLockedException(e.getMessage(), e);
    } catch (IOException e) {
      throw new PersistenceException(e.getMessage(), e);
    }
  }

  /*
   * @see org.rssowl.core.model.search.IModelSearch#shutdown()
   */
  public void shutdown(boolean emergency) throws PersistenceException {
    try {

      /*
       * Close fIndexer first because it's more important (reduces the chance of
       * a corrupt index). Can be null if exception thrown during start-up
       */
      if (fIndexer != null)
        fIndexer.shutdown(emergency);

      /*
       * We don't bother to close searchers if it's an emergency. They will be
       * released when the process exits.
       */
      if (emergency)
        return;

      synchronized (this) {

        /* We first close all the searchers whose refCount is 0 */
        for (Map.Entry<IndexSearcher, AtomicInteger> mapEntry : fSearchers.entrySet()) {
          if (mapEntry.getValue().get() == 0)
            dispose(mapEntry.getKey());
        }

        while (!fSearchers.isEmpty()) {
          try {

            /*
             * We sleep with a lock held because the Threads that we're waiting
             * to make progress don't acquire a lock
             */
            Thread.sleep(50);
          }

          /* If interrupted, we just leave the rest of the searchers open */
          catch (InterruptedException e) {
            return;
          }

          /* Try again for the ones that are left */
          for (Map.Entry<IndexSearcher, AtomicInteger> mapEntry : fSearchers.entrySet()) {
            if (mapEntry.getValue().get() == 0)
              dispose(mapEntry.getKey());
          }
        }

        fSearcher = null;
      }
    } catch (IOException e) {
      throw new PersistenceException(e.getMessage(), e);
    }
  }

  private BooleanClause createIsCopyTermQuery(boolean copy) {
    String field = String.valueOf(INews.PARENT_ID);
    TermQuery termQuery = new TermQuery(new Term(field, NumberTools.longToString(0)));
    Occur occur = copy ? Occur.MUST_NOT : Occur.MUST;
    return new BooleanClause(termQuery, occur);
  }

  private static final class SimpleHitCollector extends HitCollector {

    private final IndexSearcher fSearcher;
    private final List<NewsReference> fResultList;
    private final Map<Long, Long> fSearchResultNewsIds = new HashMap<Long, Long>();

    SimpleHitCollector(IndexSearcher searcher, List<NewsReference> resultList) {
      fSearcher = searcher;
      fResultList = resultList;
    }

    @Override
    public void collect(int doc, float score) {
      try {
        Document document = fSearcher.doc(doc);

        /* Receive Stored Fields */
        long newsId = Long.parseLong(document.get(SearchDocument.ENTITY_ID_TEXT));

        /*
         * Under some circumstances the index might contain the same news twice.
         * This can happen in situations where RSSOwl is quitting in an emergent
         * way (e.g. the OS shutting down while RSSOwl is running). To avoid
         * issues, we filter out duplicate results from the search. See
         * http://dev.rssowl.org/show_bug.cgi?id=1264
         */
        if (!fSearchResultNewsIds.containsKey(newsId)) {
          fResultList.add(new NewsReference(newsId));
          fSearchResultNewsIds.put(newsId, newsId);
        }
      } catch (IOException e) {
        Activator.safeLogError(e.getMessage(), e);
      }
    }
  }

  /**
   * @param guids the List of {@link IGuid} to search news for.
   * @param copy If <code>true</code>, only consider copied News.
   * @param monitor to react on cancellation
   * @return a List of {@link NewsReference} matching the given search and
   * grouped by {@link IGuid}.
   */
  public Map<IGuid, List<NewsReference>> searchNewsByGuids(List<IGuid> guids, boolean copy, IProgressMonitor monitor) {
    Map<IGuid, List<NewsReference>> linkToRefs = new HashMap<IGuid, List<NewsReference>>(guids.size());
    IndexSearcher currentSearcher = getCurrentSearcher();
    try {
      for (IGuid guid : guids) {

        /* Return early on cancellation */
        if (monitor.isCanceled())
          return linkToRefs;

        BooleanQuery query = createGuidQuery(guid, copy);
        List<NewsReference> newsRefs = simpleSearch(currentSearcher, query);
        if (!newsRefs.isEmpty())
          linkToRefs.put(guid, newsRefs);
      }
      return linkToRefs;
    } finally {
      disposeIfNecessary(currentSearcher);
    }
  }

  /**
   * @param links The Links to search news for.
   * @param copy If <code>true</code>, only consider copied News.
   * @param monitor to react on cancellation
   * @return a List of {@link NewsReference} matching the given search and
   * grouped by the {@link URI}.
   */
  public Map<URI, List<NewsReference>> searchNewsByLinks(List<URI> links, boolean copy, IProgressMonitor monitor) {
    Map<URI, List<NewsReference>> linkToRefs = new HashMap<URI, List<NewsReference>>(links.size());
    IndexSearcher currentSearcher = getCurrentSearcher();
    try {
      for (URI link : links) {

        /* Return early on cancellation */
        if (monitor.isCanceled())
          return linkToRefs;

        BooleanQuery query = createNewsByLinkBooleanQuery(link, copy);
        List<NewsReference> newsRefs = simpleSearch(currentSearcher, query);
        if (!newsRefs.isEmpty())
          linkToRefs.put(link, newsRefs);
      }
      return linkToRefs;
    } finally {
      disposeIfNecessary(currentSearcher);
    }
  }

  /**
   * @param link The Link to search news for.
   * @param copy If <code>true</code>, only consider copied News.
   * @return a List of {@link NewsReference} matching the given search.
   */
  public List<NewsReference> searchNewsByLink(URI link, boolean copy) {
    Assert.isNotNull(link, "link"); //$NON-NLS-1$
    BooleanQuery query = createNewsByLinkBooleanQuery(link, copy);
    return simpleSearch(query);
  }

  private BooleanQuery createNewsByLinkBooleanQuery(URI link, boolean copy) {
    BooleanQuery query = new BooleanQuery(true);
    query.add(new TermQuery(new Term(String.valueOf(INews.LINK), link.toString().toLowerCase())), Occur.MUST);
    query.add(createIsCopyTermQuery(copy));
    return query;
  }

  /**
   * @param guid the {@link IGuid} to search news for.
   * @param copy If <code>true</code>, only consider copied News.
   * @return a List of {@link NewsReference} matching the given search.
   */
  public List<NewsReference> searchNewsByGuid(IGuid guid, boolean copy) {
    Assert.isNotNull(guid, "guid"); //$NON-NLS-1$
    BooleanQuery query = createGuidQuery(guid, copy);
    return simpleSearch(query);
  }

  private BooleanQuery createGuidQuery(IGuid guid, boolean copy) {
    BooleanQuery query = new BooleanQuery(true);
    query.add(new TermQuery(new Term(String.valueOf(INews.GUID), guid.getValue().toLowerCase())), Occur.MUST);
    query.add(createIsCopyTermQuery(copy));
    return query;
  }

  private List<NewsReference> simpleSearch(Query query) {
    /* Make sure the searcher is in sync */
    IndexSearcher currentSearcher = getCurrentSearcher();
    try {
      List<NewsReference> newsRefs = simpleSearch(currentSearcher, query);
      return newsRefs;
    } finally {
      disposeIfNecessary(currentSearcher);
    }
  }

  private List<NewsReference> simpleSearch(IndexSearcher currentSearcher, Query query) {
    List<NewsReference> resultList = new ArrayList<NewsReference>(2);

    /* Use custom hit collector for performance reasons */
    try {
      currentSearcher.search(query, new SimpleHitCollector(currentSearcher, resultList));
      return resultList;
    } catch (IOException e) {
      throw new PersistenceException(e);
    }
  }

  private void disposeIfNecessary(IndexSearcher currentSearcher) {
    AtomicInteger referenceCount = fSearchers.get(currentSearcher);
    if (referenceCount.decrementAndGet() == 0 && fSearcher != currentSearcher) {
      try {

        /*
         * May be called by getCurrentSearcher at the same time, but safe
         * because dispose is safe to be called many times for the same
         * searcher.
         */
        dispose(currentSearcher);
      } catch (IOException e) {
        throw new PersistenceException(e);
      }
    }
  }

  /*
   * @see
   * org.rssowl.core.persist.service.IModelSearch#searchNews(org.rssowl.core
   * .persist.ISearch)
   */
  public List<SearchHit<NewsReference>> searchNews(ISearch search) throws PersistenceException {
    return searchNews(search.getSearchConditions(), search.matchAllConditions());
  }

  /*
   * @see org.rssowl.core.model.search.IModelSearch#searchNews(java.util.List,
   * boolean)
   */
  public List<SearchHit<NewsReference>> searchNews(Collection<ISearchCondition> conditions, boolean matchAllConditions) throws PersistenceException {
    return searchNews(conditions, null, matchAllConditions);
  }

  /*
   * @see
   * org.rssowl.core.persist.service.IModelSearch#searchNews(java.util.Collection
   * , org.rssowl.core.persist.ISearchCondition, boolean)
   */
  public List<SearchHit<NewsReference>> searchNews(Collection<ISearchCondition> conditions, ISearchCondition scope, boolean matchAllConditions) throws PersistenceException {
    try {
      return doSearchNews(conditions, scope, matchAllConditions);
    }

    /* Too Many Clauses - Increase Clauses Limit */
    catch (TooManyClauses e) {

      /* Disable Clauses Limit */
      if (BooleanQuery.getMaxClauseCount() != ModelSearchImpl.MAX_CLAUSE_COUNT) {
        BooleanQuery.setMaxClauseCount(MAX_CLAUSE_COUNT);
        return doSearchNews(conditions, scope, matchAllConditions);
      }

      /* Maximum reached */
      throw new PersistenceException(Messages.ModelSearchImpl_ERROR_WILDCARDS, e);
    }
  }

  private List<SearchHit<NewsReference>> doSearchNews(Collection<ISearchCondition> conditions, ISearchCondition scope, boolean matchAllConditions) throws PersistenceException {

    /* Perform the search */
    try {
      Query bQuery = ModelSearchQueries.createQuery(conditions, scope, matchAllConditions);

      /* Make sure the searcher is in sync */
      final IndexSearcher currentSearcher = getCurrentSearcher();
      final List<SearchHit<NewsReference>> resultList = new ArrayList<SearchHit<NewsReference>>();
      final Map<Long, Long> searchResultNewsIds = new HashMap<Long, Long>();

      /* Use custom hit collector for performance reasons */
      HitCollector collector = new HitCollector() {
        @Override
        public void collect(int doc, float score) {
          try {
            Document document = currentSearcher.doc(doc);

            /* Receive Stored Fields */
            long newsId = Long.parseLong(document.get(SearchDocument.ENTITY_ID_TEXT));
            INews.State newsState = NEWS_STATES[Integer.parseInt(document.get(NewsDocument.STATE_ID_TEXT))];

            Map<Integer, INews.State> data = new HashMap<Integer, INews.State>(1);
            data.put(INews.STATE, newsState);

            /*
             * Under some circumstances the index might contain the same news
             * twice. This can happen in situations where RSSOwl is quitting in
             * an emergent way (e.g. the OS shutting down while RSSOwl is
             * running). To avoid issues, we filter out duplicate results from
             * the search. See http://dev.rssowl.org/show_bug.cgi?id=1264
             */
            if (!searchResultNewsIds.containsKey(newsId)) {
              resultList.add(new SearchHit<NewsReference>(new NewsReference(newsId), score, data));
              searchResultNewsIds.put(newsId, newsId);
            }
          } catch (IOException e) {
            Activator.safeLogError(e.getMessage(), e);
          }
        }
      };

      /* Perform the Search */
      try {
        currentSearcher.search(bQuery, collector);
        return resultList;
      } finally {
        disposeIfNecessary(currentSearcher);
      }
    } catch (IOException e) {
      throw new PersistenceException(Messages.ModelSearchImpl_ERROR_SEARCH, e);
    }
  }

  private IndexSearcher createIndexSearcher() throws CorruptIndexException, IOException {
    IndexSearcher searcher = new IndexSearcher(IndexReader.open(fDirectory));
    fSearchers.put(searcher, new AtomicInteger(0));
    return searcher;
  }

  private IndexSearcher getCurrentSearcher() throws PersistenceException {
    try {
      boolean flushed = fIndexer.flushIfNecessary();

      /* Get the current searcher before acquiring lock in case we block */
      IndexSearcher currentSearcher = fSearcher;

      synchronized (this) {

        /*
         * If there are changes and currentSearcher == fSearcher, it means we
         * won the race for the lock, so we reopen the searcher. If flushed is
         * true, but currentSearcher != fSearcher it means that another thread
         * has reopened the reader while we were blocked waiting for the lock.
         */
        if (flushed && currentSearcher == fSearcher) {
          IndexReader currentReader = fSearcher.getIndexReader();
          IndexReader newReader = currentReader.reopen();
          if (newReader != currentReader) {

            IndexSearcher newSearcher = new IndexSearcher(newReader);
            fSearchers.put(newSearcher, new AtomicInteger(1));

            /*
             * Assign to field before we check the referenceCount to ensure that
             * disposeIfNecessary will dispose the searcher if it has the last
             * reference, is yet to check if fSearcher has been changed (if this
             * was done after referenceCount.get() == 0, we could leak a
             * searcher).
             */
            fSearcher = newSearcher;

            AtomicInteger referenceCount = fSearchers.get(currentSearcher);
            if (referenceCount != null && referenceCount.get() == 0) {

              /*
               * May be called by disposeIfNecessary at the same time, but safe
               * because dispose is safe to be called many times for the same
               * searcher.
               */
              dispose(currentSearcher);
            }

            return fSearcher;
          }
        }
        fSearchers.get(fSearcher).incrementAndGet();
        return fSearcher;
      }
    } catch (IOException e) {
      throw new PersistenceException(e.getMessage(), e);
    }
  }

  /**
   * Can be called multiple times safely because: - close is safe to be called
   * many times in IndexReader and IndexSearcher - No IndexSearcher is ever
   * added again into the fSearchers map so calling remove two or more times is
   * harmless.
   */
  private void dispose(IndexSearcher searcher) throws IOException {
    fSearchers.remove(searcher);
    searcher.close();
    searcher.getIndexReader().close();
  }

  /*
   * @see org.rssowl.core.model.search.IModelSearch#clearIndex()
   */
  public void clearIndex() throws PersistenceException {
    try {
      synchronized (this) {
        IndexSearcher currentSearcher = fSearcher;
        fIndexer.clearIndex();
        fSearcher = createIndexSearcher();

        /*
         * We block until the current reader has been closed or can be closed.
         * Most times we should be able to succeed without having to sleep.
         */
        while (true) {
          AtomicInteger refCount = fSearchers.get(currentSearcher);
          if (refCount == null)
            break;
          else if (refCount.get() == 0) {

            /*
             * This may be called at the same time from disposeIfNecessary, but
             * that's fine.
             */
            dispose(currentSearcher);
            break;
          } else {
            try {

              /*
               * We sleep with a lock held because the Threads that we're
               * waiting to make progress don't acquire a lock
               */
              Thread.sleep(100);
            } catch (InterruptedException e) {
              throw new PersistenceException("Failed to close IndexSearcher: " + fSearcher); //$NON-NLS-1$
            }
          }
        }
      }
    } catch (IOException e) {
      throw new PersistenceException(e.getMessage(), e);
    }
  }

  /*
   * @see
   * org.rssowl.core.persist.service.IModelSearch#addIndexListener(org.rssowl
   * .core.persist.service.IndexListener)
   */
  public void addIndexListener(IndexListener listener) {
    fIndexListeners.add(listener);
  }

  /*
   * @see
   * org.rssowl.core.persist.service.IModelSearch#removeIndexListener(org.rssowl
   * .core.persist.service.IndexListener)
   */
  public void removeIndexListener(IndexListener listener) {
    fIndexListeners.remove(listener);
  }

  /*
   * @see org.rssowl.core.persist.service.IModelSearch#optimize()
   */
  public void optimize() {
    try {
      fIndexer.optimize();
    } catch (IOException e) {
      throw new PersistenceException(e.getMessage(), e);
    }
  }

  void notifyIndexUpdated(int docCount) {
    for (IndexListener listener : fIndexListeners) {
      listener.indexUpdated(docCount);
    }
  }

  /*
   * @see org.rssowl.core.persist.service.IModelSearch#reIndexOnNextStartup()
   */
  public void reIndexOnNextStartup() throws PersistenceException {
    try {
      DBManager.getDefault().getReIndexFile().createNewFile();
    } catch (IOException e) {
      throw new PersistenceException(e);
    }
  }

  /*
   * @see
   * org.rssowl.core.persist.service.IModelSearch#reindexAll(org.eclipse.core
   * .runtime.IProgressMonitor)
   */
  public void reindexAll(IProgressMonitor monitor) throws PersistenceException {

    /* May be used before Owl is completely set-up */
    Collection<INews> newsList = InternalOwl.getDefault().getPersistenceService().getDAOService().getNewsDAO().loadAll();

    /* User might have cancelled the operation */
    if (monitor.isCanceled())
      return;

    /* Begin Task */
    monitor.beginTask(Messages.ModelSearchImpl_PROGRESS_WAIT, newsList.size());
    monitor.subTask(Messages.ModelSearchImpl_REINDEX_SEARCH_INDEX);

    /* Reindex in Chunks to reduce memory consumption */
    reindexInChunks(newsList.iterator(), monitor);

    /* Finished */
    monitor.done();
  }

  private void reindexInChunks(Iterator<INews> iterator, IProgressMonitor monitor) {
    boolean isFirstRun = true;

    /* User might have cancelled the operation */
    if (monitor.isCanceled())
      return;

    /* Startup Modelsearch and clean the index directory */
    startup(true);

    /* Lock the indexer for the duration of the reindexing */
    synchronized (fIndexer) {
      boolean userCanceled = false;

      /* Delete the Index first */
      clearIndex();

      /* Proceed until finished indexing all News Items */
      while (iterator.hasNext()) {

        /* Obtain the next chunk of news from the List */
        List<INews> newsChunkToBeIndexed = new ArrayList<INews>(INDEX_CHUNK_SIZE);
        for (int i = 0; i < INDEX_CHUNK_SIZE && iterator.hasNext(); i++)
          newsChunkToBeIndexed.add(iterator.next());

        /* Return if nothing to do */
        if (newsChunkToBeIndexed.isEmpty())
          break;

        /* Flush frequently to optimize memory usage during reindexing */
        if (!isFirstRun)
          fIndexer.flushIfNecessary();

        /* Index News Items */
        for (INews newsitem : newsChunkToBeIndexed) {

          /* User might have canceled, so give feedback that work needs to complete */
          if (!userCanceled && monitor.isCanceled()) {
            monitor.setTaskName(Messages.ModelSearchImpl_WAIT_TASK_COMPLETION);
            userCanceled = true;
          }

          /* We don't pass the whole list at once to be able to report progress. */
          List<INews> indexList = new ArrayList<INews>(1);
          indexList.add(newsitem);
          fIndexer.index(indexList, false, false); //Disable ACID Support
          monitor.worked(1);
        }

        isFirstRun = false;
      }
    }

    /* Finally we refresh the searchers (this will trigger flushIfNecessary()) */
    IndexSearcher currentSearcher = null;
    try {
      currentSearcher = getCurrentSearcher();
    } finally {
      if (currentSearcher != null)
        disposeIfNecessary(currentSearcher);
    }
  }

  /*
   * @see
   * org.rssowl.core.persist.service.IModelSearch#cleanUp(org.eclipse.core.runtime
   * .IProgressMonitor)
   */
  public void cleanUp(IProgressMonitor monitor) throws PersistenceException {

    /* Retrieve all News from the Index */
    List<NewsReference> results = simpleSearch(new MatchAllDocsQuery());

    /* User might have cancelled the operation */
    if (monitor.isCanceled())
      return;

    /* Begin Task */
    monitor.beginTask(Messages.ModelSearchImpl_PROGRESS_WAIT, results.size());
    monitor.subTask(Messages.ModelSearchImpl_CLEANUP_SEARCH_INDEX);

    /* Find News to delete */
    Set<NewsReference> newsToDelete = new HashSet<NewsReference>();
    INewsDAO newsDao = InternalOwl.getDefault().getPersistenceService().getDAOService().getNewsDAO();
    for (NewsReference newsRef : results) {

      /* User might have cancelled the operation */
      if (monitor.isCanceled())
        return;

      /* Delete if news no longer exists in DB */
      if (!newsDao.exists(newsRef.getId()))
        newsToDelete.add(newsRef);

      /* Delete if news either NULL or not visible */
      else {
        INews resolvedNews = newsDao.load(newsRef.getId());
        if (resolvedNews == null || !resolvedNews.isVisible())
          newsToDelete.add(newsRef);
      }

      /* Report Progress */
      monitor.worked(1);
    }

    /* Remove News to Delete from Index */
    synchronized (fIndexer) {
      try {
        fIndexer.removeFromIndex(newsToDelete);
      } catch (IOException e) {
        throw new PersistenceException(e.getMessage(), e);
      }
    }

    /* Finished */
    monitor.done();
  }

  /*
   * @see org.rssowl.core.persist.service.IModelSearch#cleanUpOnNextStartup()
   */
  public void cleanUpOnNextStartup() throws PersistenceException {
    try {
      DBManager.getDefault().getCleanUpIndexFile().createNewFile();
    } catch (IOException e) {
      throw new PersistenceException(e);
    }
  }
}
TOP

Related Classes of org.rssowl.core.internal.persist.search.ModelSearchImpl$SimpleHitCollector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.