// BlogBridge -- RSS feed reader, manager, and web based service // Copyright (C) 2002-2006 by R. Pito Salas // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software Foundation; // either version 2 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with this program; // if not, write to the Free Software Foundation, Inc., 59 Temple Place, // Suite 330, Boston, MA 02111-1307 USA // // Contact: R. Pito Salas // mailto:pitosalas@users.sourceforge.net // More information: about BlogBridge // http://www.blogbridge.com // http://sourceforge.net/projects/blogbridge // // $Id: SearchFeedsManager.java,v 1.26 2008/02/28 09:58:33 spyromus Exp $ // package com.salas.bb.core; import EDU.oswego.cs.dl.util.concurrent.Executor; import com.jgoodies.uif.application.Application; import com.salas.bb.domain.*; import com.salas.bb.domain.events.FeedRemovedEvent; import com.salas.bb.domain.query.articles.Query; import com.salas.bb.domain.utils.DomainAdapter; import com.salas.bb.utils.concurrency.ExecutorFactory; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.prefs.Preferences; /** * Manager of all search feeds, which is doing scans for them. */ public class SearchFeedsManager extends DomainAdapter { private static final int KEEP_ALIVE_TIME = 10000; private static Boolean dontUpdateAutomatically; private GuidesSet guidesSet; private Set<SearchFeed> searchFeeds; private Executor executor; /** * Creates feeds manager of some guides set. * * @param aSet guides set to work with. */ public SearchFeedsManager(GuidesSet aSet) { guidesSet = aSet; searchFeeds = new CopyOnWriteArraySet<SearchFeed>(); executor = ExecutorFactory.createPooledExecutor("Search Feeds", 1, Thread.MIN_PRIORITY, KEEP_ALIVE_TIME); loadCurrentSearchFeedsInMap(); } private void loadCurrentSearchFeedsInMap() { StandardGuide[] guides = guidesSet.getStandardGuides(null); for (StandardGuide guide : guides) collectSearchFeedsInGuide(guide); } private void collectSearchFeedsInGuide(StandardGuide aGuide) { IFeed[] feeds = aGuide.getFeeds(); for (IFeed feed : feeds) { if (feed instanceof SearchFeed) searchFeeds.add((SearchFeed)feed); } } public void runAllQueries() { // List<Integer> sids = new ArrayList<Integer>(); // // StandardGuide[] guides = guidesSet.getStandardGuides(null); // for (StandardGuide guide : guides) // { // IFeed[] feeds = guide.getFeeds(); // for (IFeed feed : feeds) // { // if (feed instanceof SearchFeed) // { // Integer id = System.identityHashCode(feed); // if (!sids.contains(id)) // { // sids.add(id); // runQuery((SearchFeed)feed); // } // } // } // } Thread.currentThread().setPriority(Thread.MIN_PRIORITY); SearchFeed[] sfeeds = searchFeeds.toArray(new SearchFeed[0]); processingStarted(sfeeds); try { List<IFeed> feeds = guidesSet.getFeedsList().getFeeds(); for (IFeed feed : feeds) { if (feed instanceof DataFeed) { for (SearchFeed sfeed : sfeeds) scanFeed(feed, sfeed); try { Thread.sleep(100); } catch (InterruptedException e) { // Ignore } } } } finally { processingFinished(sfeeds); } } private void processingStarted(SearchFeed[] sfeeds) { for (SearchFeed sfeed : sfeeds) sfeed.processingStarted(); } private void processingFinished(SearchFeed[] sfeeds) { for (SearchFeed sfeed : sfeeds) sfeed.processingFinished(); } /** * Returns all search feeds that have sentiments clause in them. * * @return feeds. */ public static List<SearchFeed> findFeedsWithSentimentsClause() { return GlobalController.getSearchFeedsManager().findFeedsWithSentimentsClause0(); } /** * Returns all search feeds that have sentiments clause in them. * * @return feeds. */ private List<SearchFeed> findFeedsWithSentimentsClause0() { List<SearchFeed> feeds = new LinkedList<SearchFeed>(); for (SearchFeed feed : searchFeeds) { Query query = feed.getQuery(); if (query.hasSentimentsClause()) feeds.add(feed); } return feeds; } /** * Runs query for a given search feed. Updates the list of articles. * * @param aSearchFeed search feed. */ public void runQuery(SearchFeed aSearchFeed) { aSearchFeed.processingStarted(); try { aSearchFeed.reviewArticlesTakenFrom(null); StandardGuide[] guides = guidesSet.getStandardGuides(null); for (StandardGuide guide : guides) scanGuide(guide, aSearchFeed); } finally { aSearchFeed.processingFinished(); } } private void scanGuide(StandardGuide aGuide, SearchFeed aSearchFeed) { IFeed[] feeds = aGuide.getFeeds(); for (IFeed feed : feeds) if (feed instanceof DataFeed) scanFeed(feed, aSearchFeed); } private void scanFeed(IFeed aFeed, SearchFeed aSearchFeed) { IArticle[] articles = aFeed.getArticles(); for (IArticle article : articles) aSearchFeed.addArticleIfMatching(article); } // --------------------------------------------------------------------------------------------- // Listening to domain events // --------------------------------------------------------------------------------------------- /** * Invoked when new article has been added to the feed. * * @param feed feed. * @param article article. */ public void articleAdded(IFeed feed, IArticle article) { if (feed instanceof SearchFeed || isNotUpdatingAutomatically()) return; scheduleOrRun(new ProcessArticleAdded(article)); } /** * Returns <code>TRUE</code> only if there's a property set that * doesn't allow search feeds to be updated dynamically. * * @return <code>TRUE</code> to not update search feeds. */ private static synchronized boolean isNotUpdatingAutomatically() { if (dontUpdateAutomatically == null) { Preferences prefs = Application.getUserPreferences(); dontUpdateAutomatically = prefs.getBoolean("searchfeeds.dontUpdateAutomatically", false); } return dontUpdateAutomatically; } /** * Invoked when the article has been removed from the feed. * * @param feed feed. * @param article article. */ public void articleRemoved(IFeed feed, IArticle article) { scheduleOrRun(new ProcessArticleRemoved(article)); } /** * Schedule the task for execution or run if unable to schedule. * * @param task task. */ private void scheduleOrRun(Runnable task) { try { executor.execute(task); } catch (InterruptedException e) { task.run(); } } /** * Invoked when new guide has been added to the set. * * @param set guides set. * @param guide added guide. * @param lastInBatch <code>TRUE</code> when this is the last even in batch. */ public void guideAdded(GuidesSet set, IGuide guide, boolean lastInBatch) { if (guide instanceof StandardGuide) { collectSearchFeedsInGuide((StandardGuide)guide); } } /** * Invoked when new feed has been added to the guide. * * @param guide parent guide. * @param feed added feed. */ public void feedAdded(IGuide guide, IFeed feed) { if (feed instanceof SearchFeed) searchFeeds.add((SearchFeed)feed); } /** * Invoked when the feed has been removed from the guide. * * @param event feed removal event. */ public void feedRemoved(FeedRemovedEvent event) { IFeed feed = event.getFeed(); if (feed.getParentGuides().length == 0) { if (feed instanceof SearchFeed) { SearchFeed sfeed = (SearchFeed)feed; sfeed.unregisterListeners(); searchFeeds.remove(sfeed); } else if (feed instanceof DataFeed) { IArticle[] articles = feed.getArticles(); for (IArticle article : articles) articleRemoved(feed, article); } } } /** * Invoked when the property of the feed has been changed. * * @param feed feed. * @param property property of the feed. * @param oldValue old property value. * @param newValue new property value. */ public void propertyChanged(IFeed feed, String property, Object oldValue, Object newValue) { if (feed instanceof SearchFeed) { SearchFeed sfeed = (SearchFeed)feed; if (SearchFeed.PROP_QUERY.equals(property) || SearchFeed.PROP_DEDUP_UPDATED.equals(property)) { runQuery(sfeed); } } else if (isInterestingFeedProperty(property)) { for (SearchFeed searchFeed : searchFeeds) { searchFeed.reviewArticlesTakenFrom(feed); scanFeed(feed, searchFeed); } } } /** * Invoked when the property of the article has been changed. * * @param article article. * @param property property of the article. * @param oldValue old property value. * @param newValue new property value. */ // This one creates a deadlock on startup see (BT #248) // public void propertyChanged(IArticle article, String property, Object oldValue, Object newValue) // { // Iterator iterator = searchFeeds.iterator(); // while (iterator.hasNext()) // { // SearchFeed searchFeed = (SearchFeed)iterator.next(); // searchFeed.addArticleIfMatching(article); // } // } /** * Far not all properties should induce review of results of a query. In this * method we check if a property of the feed is interesting to us. * * @param aProperty name of feed property. * * @return <code>TRUE</code> if the change of this property is interesting to search feeds. */ private boolean isInterestingFeedProperty(String aProperty) { return aProperty.equals(IFeed.PROP_TITLE) || aProperty.equals(IFeed.PROP_RATING) || aProperty.equals(DirectFeed.PROP_USER_TAGS); } /** * Reviews whole search feed. * * @param aSearchFeed search feed with changed query criteria. */ public void queryUpdated(SearchFeed aSearchFeed) { aSearchFeed.reviewArticlesTakenFrom(null); } /** * Updates a search feed. * * @param feed feed. */ public static void update(SearchFeed feed) { GlobalController.getSearchFeedsManager().runQuery(feed); } /** * Processes article removed event. Walks through the list of search feeds and * notifies them one by one. */ private class ProcessArticleAdded implements Runnable { private final IArticle article; /** * Creates the task with an article. * * @param article article. */ public ProcessArticleAdded(IArticle article) { this.article = article; } /** * Executed when processing should start. */ public void run() { for (SearchFeed searchFeed : searchFeeds) searchFeed.addArticleIfMatching(article); } } /** * Processes article removed event. Walks through the list of search feeds and * notifies them one by one. */ private class ProcessArticleRemoved implements Runnable { private final IArticle article; /** * Creates the task with an article. * * @param article article. */ public ProcessArticleRemoved(IArticle article) { this.article = article; } /** * Executed when processing should start. */ public void run() { for (SearchFeed searchFeed : searchFeeds) searchFeed.removeArticle(article); } } }