// 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: QueryFeed.java,v 1.47 2007/07/06 14:47:57 spyromus Exp $ // package com.salas.bb.domain; import EDU.oswego.cs.dl.util.concurrent.Mutex; import EDU.oswego.cs.dl.util.concurrent.Sync; import com.salas.bb.domain.querytypes.QueryType; import com.salas.bb.utils.StringUtils; import com.salas.bb.utils.i18n.Strings; import com.salas.bb.utils.parser.Channel; import com.salas.bb.views.feeds.IFeedDisplayConstants; import java.io.IOException; import java.net.URL; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; /** * Query feed uses third-party services to get the list of articles. Each query feed has * query type desriminator assigned. Using query type object feed can convert the query * parameter and some other properties into the valid query URL which can be used * to get the data from service. */ public class QueryFeed extends NetworkFeed { private static final Logger LOG = Logger.getLogger(QueryFeed.class.getName()); /** Query type property. */ public static final String PROP_QUERY_TYPE = "queryType"; /** Parameter property. */ public static final String PROP_PARAMETER = "parameter"; public static final String PROP_DEDUP_ENABLED = "dedupEnabled"; public static final String PROP_DEDUP_FROM = "dedupFrom"; public static final String PROP_DEDUP_TO = "dedupTo"; private final Sync syncParameterChange; private String baseTitle; private String title; private QueryType queryType; private String parameter; /** TRUE when deduplication functionality is enabled. */ private boolean dedupEnabled; /** The first word index to look for the match. */ private int dedupFrom; /** The last word index to look for the match. */ private int dedupTo; /** * Creates query feed. */ public QueryFeed() { syncParameterChange = new Mutex(); baseTitle = null; queryType = null; parameter = null; renderTitle(); super.setCustomViewModeEnabled(true); dedupEnabled = false; dedupFrom = 0; dedupTo = 0; } /** * Returns reason for being invalid. Query feeds are always invalid when their type isn't * specified. * * @return reason. */ public String getInvalidnessReason() { String reason = null; if (queryType == null) reason = Strings.message("feed.invalidness.reason.unsupported.query"); if (reason == null) reason = super.getInvalidnessReason(); return reason; } /** * Returns TRUE if this feed is updatable, meaning that it's not invalid for some reason and * it's proper time to call <code>update()</code> method. The behaviod may differ if the update * operation was called directly to this particular feed and not as a part of a bigger update * operation (update guide or update all). * * @param direct if TRUE then the update was requested directly (not through guide/set or by * periodic check). */ protected boolean isUpdatable(boolean direct) { return queryType != null && StringUtils.isNotEmpty(parameter) && super.isUpdatable(direct); } /** * Returns title of feed. * * @return title. */ public String getTitle() { return title; } /** * Renders new title on type or title change. For now, all we do is to append "SmartFeed:" to the name. */ private void renderTitle() { title = getBaseTitle(); } /** * Returns title of feed. * * @return title. */ public String getBaseTitle() { return baseTitle; } /** * Sets the title of the feed. * * @param aTitle title of the feed. */ public void setBaseTitle(String aTitle) { String oldTitle = getTitle(); baseTitle = aTitle; renderTitle(); firePropertyChanged(PROP_TITLE, oldTitle, getTitle()); } /** * Returns query type. * * @return query type. */ public QueryType getQueryType() { return queryType; } /** * Sets new query type. * * @param aQueryType query type. */ public void setQueryType(QueryType aQueryType) { int viewMode = aQueryType == null ? IFeedDisplayConstants.MODE_BRIEF : aQueryType.getPreferredViewMode(); setCustomViewMode(viewMode); String oldTitle = getTitle(); QueryType old = queryType; queryType = aQueryType; renderTitle(); firePropertyChanged(PROP_QUERY_TYPE, old, queryType, true, false); firePropertyChanged(PROP_TITLE, oldTitle, getTitle()); if (queryType != null) setType(queryType.getFeedType()); } /** * Returns the query parameter. * * @return parameter. */ public String getParameter() { return parameter; } /** * Sets the query parameter. * * @param aParameter parameter. */ public void setParameter(String aParameter) { String old = parameter; parameter = aParameter; firePropertyChanged(PROP_PARAMETER, old, parameter, true, false); } /** * Sets the options at once and updates the feed if necessary. * * @param dedupEnabled deduplication enabled flag. * @param dedupFrom the first dedup word. * @param dedupTo the last dedup word. * * @return <code>TRUE</code> if changed and updated. */ public boolean setDedupProperties(boolean dedupEnabled, int dedupFrom, int dedupTo) { boolean oldEnabled = isDedupEnabled(); int oldFrom = getDedupFrom(); int oldTo = getDedupTo(); setDedupEnabled(dedupEnabled); setDedupFrom(dedupFrom); setDedupTo(dedupTo); return (oldEnabled != isDedupEnabled() || oldFrom != getDedupFrom() || oldTo != getDedupTo()); } /** * Changes query parameter to something different and resets the feed statistics, * which will force it to be updated. * * @param aNewParameter new parameter. * * @return TRUE if parameter has been changed. */ public boolean changeParameter(String aNewParameter) { boolean changed = false; try { syncParameterChange.acquire(); try { if (parameter == null || !parameter.equals(aNewParameter)) { setParameter(aNewParameter); clear(false); resetFeedStatistics(); changed = true; } } finally { syncParameterChange.release(); } } catch (InterruptedException e) { LOG.log(Level.SEVERE, Strings.error("interrupted"), e); } return changed; } /** * Reviews articles with dup-checking. */ public void reviewArticles() { clear(true); } /** * Removes all articles. * * @param checkDup <code>TRUE</code> to clear with dup-checking. */ private void clear(boolean checkDup) { if (checkDup && !isDedupEnabled()) return; int count = getArticlesCount(); for (int i = 0; i < count; i++) { IArticle article = getArticleAt(count - i - 1); if (!article.isPinned() && (!checkDup || isDuplicate(article, getDedupFrom(), getDedupTo(), getArticlesList()))) { removeArticle(article); } } } /** * Gets XML URL. * * @return URL. */ public URL getXmlURL() { int limit = getPurgeLimitCombined(); return queryType == null ? null : queryType.convertToURL(parameter, limit); } /** * Fetches the feed by some specific means. * * @return the feed or NULL if there was an error or no updates required. */ protected Channel fetchFeed() throws IOException { Channel result = queryType.fetchFeed(this); if (result == null) result = super.fetchFeed(); return result; } @Override public boolean insertArticle(int index, IArticle article) { if (queryType != null) article = queryType.beforeInsertArticle(index, article); return super.insertArticle(index, article); } /** * Returns simple match key, which can be used to detect similarity of feeds. For example, it's * XML URL for the direct feeds, query type + parameter for the query feeds, serialized search * criteria for the search feeds. * * @return match key. */ public String getMatchKey() { return "QF" + (queryType == null ? "" : queryType.getType()) + " " + parameter; } /** * Returns <code>TRUE</code> if an article is duplicate of some other already registered. * * @param article article. * * @return <code>TRUE</code> if an article is duplicate of some other already registered. */ protected boolean isDuplicate(IArticle article) { return dedupEnabled && isDuplicate(article, dedupFrom, dedupTo, Arrays.asList(getArticles())); } // ------------------------------------------------------------------------ // Duplicates Checking // ------------------------------------------------------------------------ /** * Returns <code>TRUE</code> if remove duplicates is enabled. * * @return <code>TRUE</code> if remove duplicates is enabled. */ public boolean isDedupEnabled() { return dedupEnabled; } /** * Sets remove duplicates flag. * * @param flag <code>TRUE</code> if remove duplicates is enabled. */ public void setDedupEnabled(boolean flag) { boolean old = dedupEnabled; if (old == flag) return; dedupEnabled = flag; firePropertyChanged(PROP_DEDUP_ENABLED, old, flag, true, false); } /** * Returns the first word to look for duplicates. * * @return word number. */ public int getDedupFrom() { return dedupFrom; } /** * Sets the first word to look for duplicates. * * @param word number */ public void setDedupFrom(int word) { int old = dedupFrom; dedupFrom = word; firePropertyChanged(PROP_DEDUP_FROM, old, word, true, false); } /** * Returns the last word to look for duplicates. * * @return word number. */ public int getDedupTo() { return dedupTo; } /** * Sets the last word to look for duplicates. * * @param word number */ public void setDedupTo(int word) { int old = dedupTo; dedupTo = word; firePropertyChanged(PROP_DEDUP_TO, old, word, true, false); } }