001: /*---------------------------------------------------------------------------*\
002: $Id: MaxArticlesPlugIn.java 7041 2007-09-09 01:04:47Z bmc $
003: ---------------------------------------------------------------------------
004: This software is released under a BSD-style license:
005:
006: Copyright (c) 2004-2007 Brian M. Clapper. All rights reserved.
007:
008: Redistribution and use in source and binary forms, with or without
009: modification, are permitted provided that the following conditions are
010: met:
011:
012: 1. Redistributions of source code must retain the above copyright notice,
013: this list of conditions and the following disclaimer.
014:
015: 2. The end-user documentation included with the redistribution, if any,
016: must include the following acknowlegement:
017:
018: "This product includes software developed by Brian M. Clapper
019: (bmc@clapper.org, http://www.clapper.org/bmc/). That software is
020: copyright (c) 2004-2007 Brian M. Clapper."
021:
022: Alternately, this acknowlegement may appear in the software itself,
023: if wherever such third-party acknowlegements normally appear.
024:
025: 3. Neither the names "clapper.org", "curn", nor any of the names of the
026: project contributors may be used to endorse or promote products
027: derived from this software without prior written permission. For
028: written permission, please contact bmc@clapper.org.
029:
030: 4. Products derived from this software may not be called "curn", nor may
031: "clapper.org" appear in their names without prior written permission
032: of Brian M. Clapper.
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
036: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
037: NO EVENT SHALL BRIAN M. CLAPPER BE LIABLE FOR ANY DIRECT, INDIRECT,
038: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
039: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
040: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
041: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
042: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
043: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
044: \*---------------------------------------------------------------------------*/
045:
046: package org.clapper.curn.plugins;
047:
048: import org.clapper.curn.Constants;
049: import org.clapper.curn.CurnConfig;
050: import org.clapper.curn.CurnException;
051: import org.clapper.curn.FeedInfo;
052: import org.clapper.curn.FeedConfigItemPlugIn;
053: import org.clapper.curn.MainConfigItemPlugIn;
054: import org.clapper.curn.PostFeedParsePlugIn;
055: import org.clapper.curn.parser.RSSChannel;
056: import org.clapper.curn.parser.RSSItem;
057:
058: import org.clapper.util.classutil.ClassUtil;
059: import org.clapper.util.config.ConfigurationException;
060: import org.clapper.util.logging.Logger;
061:
062: import java.net.URL;
063:
064: import java.util.ArrayList;
065: import java.util.Collection;
066: import java.util.Map;
067: import java.util.HashMap;
068: import org.clapper.curn.FeedCache;
069:
070: /**
071: * The <tt>MaxArticlesPlugIn</tt> can be used to set an upper limit on the
072: * number of articles displayed for a feed (or for all feeds).
073: * It looks for a default (main-configuration section) "MaxArticlesToShow"
074: * parameter, and permits a per-feed "MaxArticlesToShow" parameter to override
075: * the default. This plug-in deliberately uses a non-typical sort key to force
076: * it to run <i>after</i> other stock plug-ins in the "post feed parse" phase.
077: * Thus, this plug-in doesn't apply its maximum threshold test until
078: * <i>after</i>:
079: *
080: * <ul>
081: * <li> the articles are sorted (see {@link SortArticlesPlugIn})
082: * <li> any article retention policy is applied (see
083: * {@link RetainArticlesPlugIn})
084: * <li> any "article ignore" policy is applied (see
085: * {@link IgnoreOldArticlesPlugIn})
086: * </ul>
087: *
088: *
089: * <p>This plug-in intercepts the following configuration parameters.</p>
090: *
091: * <table border="1">
092: * <tr valign="top" align="left">
093: * <th>Section</th>
094: * <th>Parameter</th>
095: * <th>Meaning</th>
096: * <th>Default</th>
097: * </tr>
098: * <tr valign="top">
099: * <td><tt>[curn]</tt></td>
100: * <td><tt>MaxArticlesToShow</tt></td>
101: * <td>Global default specifying the maximum number of articles to show
102: * per feed. Applies to all feeds that don't explicitly override this
103: * parameter.</td>
104: * <td>None. (All articles are shown, subject to actions by other
105: * plug-ins.)</td>
106: * </tr>
107: * <tr valign="top">
108: * <td><tt>[Feed<i>xxx</i>]</tt></td>
109: * <td><tt>MaxArticlesToShow</tt></td>
110: * <td>Per-feed parameter specifying the maximum number of articles to
111: * show from the feed.</td>
112: * <td>The global <tt>MaxArticlesToShow</tt> setting. If there is
113: * no global setting, then the default to show all articles in the
114: * feed (subject to actions by other plug-ins).</td>
115: * </tr>
116: * </table>
117: *
118: * @version <tt>$Revision: 7041 $</tt>
119: */
120: public class MaxArticlesPlugIn implements MainConfigItemPlugIn,
121: FeedConfigItemPlugIn, PostFeedParsePlugIn {
122: /*----------------------------------------------------------------------*\
123: Private Constants
124: \*----------------------------------------------------------------------*/
125:
126: private static final String VAR_MAX_ARTICLES = "MaxArticlesToShow";
127:
128: /*----------------------------------------------------------------------*\
129: Private Data Items
130: \*----------------------------------------------------------------------*/
131:
132: /**
133: * Feed sort-by data, by feed
134: */
135: private Map<URL, Integer> perFeedMaxArticlesMap = new HashMap<URL, Integer>();
136:
137: /**
138: * Default sort-by value
139: */
140: private Integer defaultMaxArticlesToShow = null;
141:
142: /**
143: * For log messages
144: */
145: private static final Logger log = new Logger(
146: MaxArticlesPlugIn.class);
147:
148: /*----------------------------------------------------------------------*\
149: Constructor
150: \*----------------------------------------------------------------------*/
151:
152: /**
153: * Default constructor (required).
154: */
155: public MaxArticlesPlugIn() {
156: // Nothing to do
157: }
158:
159: /*----------------------------------------------------------------------*\
160: Public Methods Required by *PlugIn Interfaces
161: \*----------------------------------------------------------------------*/
162:
163: /**
164: * Get a displayable name for the plug-in.
165: *
166: * @return the name
167: */
168: public String getPlugInName() {
169: return "Max Articles";
170: }
171:
172: /**
173: * Get the sort key for this plug-in.
174: *
175: * @return the sort key string.
176: */
177: public String getPlugInSortKey() {
178: // Unlike most plug-ins, use a sort key that forces this plug-in
179: // to run after the other stock plug-ins.
180:
181: StringBuilder key = new StringBuilder();
182: key.append("ZZZZZ.");
183: key.append(ClassUtil.getShortClassName(getClass().getName()));
184:
185: return key.toString();
186: }
187:
188: /**
189: * Initialize the plug-in. This method is called before any of the
190: * plug-in methods are called.
191: *
192: * @throws CurnException on error
193: */
194: public void initPlugIn() throws CurnException {
195: }
196:
197: /**
198: * Called immediately after <i>curn</i> has read and processed a
199: * configuration item in the main [curn] configuration section. All
200: * configuration items are passed, one by one, to each loaded plug-in.
201: * If a plug-in class is not interested in a particular configuration
202: * item, this method should simply return without doing anything. Note
203: * that some configuration items may simply be variable assignment;
204: * there's no real way to distinguish a variable assignment from a
205: * blessed configuration item.
206: *
207: * @param sectionName the name of the configuration section where
208: * the item was found
209: * @param paramName the name of the parameter
210: * @param config the {@link CurnConfig} object
211: *
212: * @throws CurnException on error
213: *
214: * @see CurnConfig
215: */
216: public void runMainConfigItemPlugIn(String sectionName,
217: String paramName, CurnConfig config) throws CurnException {
218: try {
219: if (paramName.equals(VAR_MAX_ARTICLES)) {
220: int val = config.getRequiredCardinalValue(sectionName,
221: paramName);
222: if (val <= 0) {
223: throw new ConfigurationException(
224: Constants.BUNDLE_NAME,
225: "CurnConfig.badVarValue",
226: "Section \"{0}\" in the configuration file has a bad "
227: + "value (\"{1}\") for the \"{2}\" parameter",
228: new Object[] { sectionName,
229: String.valueOf(val),
230: VAR_MAX_ARTICLES });
231: }
232:
233: defaultMaxArticlesToShow = val;
234: }
235: }
236:
237: catch (ConfigurationException ex) {
238: throw new CurnException(ex);
239: }
240: }
241:
242: /**
243: * Called immediately after <i>curn</i> has read and processed a
244: * configuration item in a "feed" configuration section. All
245: * configuration items are passed, one by one, to each loaded plug-in.
246: * If a plug-in class is not interested in a particular configuration
247: * item, this method should simply return without doing anything. Note
248: * that some configuration items may simply be variable assignment;
249: * there's no real way to distinguish a variable assignment from a
250: * blessed configuration item.
251: *
252: * @param sectionName the name of the configuration section where
253: * the item was found
254: * @param paramName the name of the parameter
255: * @param config the active configuration
256: * @param feedInfo partially complete <tt>FeedInfo</tt> object
257: * for the feed. The URL is guaranteed to be
258: * present, but no other fields are.
259: *
260: * @return <tt>true</tt> to continue processing the feed,
261: * <tt>false</tt> to skip it
262: *
263: * @throws CurnException on error
264: *
265: * @see CurnConfig
266: * @see FeedInfo
267: * @see FeedInfo#getURL
268: */
269: public boolean runFeedConfigItemPlugIn(String sectionName,
270: String paramName, CurnConfig config, FeedInfo feedInfo)
271: throws CurnException {
272: try {
273: if (paramName.equals(VAR_MAX_ARTICLES)) {
274: int val = config.getRequiredCardinalValue(sectionName,
275: paramName);
276: if (val <= 0) {
277: throw new ConfigurationException(
278: Constants.BUNDLE_NAME,
279: "CurnConfig.badVarValue",
280: "Section \"{0}\" in the configuration file has a bad "
281: + "value (\"{1}\") for the \"{2}\" parameter",
282: new Object[] { sectionName,
283: String.valueOf(val),
284: VAR_MAX_ARTICLES });
285: }
286:
287: URL feedURL = feedInfo.getURL();
288: perFeedMaxArticlesMap.put(feedURL, val);
289: log
290: .debug(feedURL + ": " + VAR_MAX_ARTICLES + "="
291: + val);
292: }
293:
294: return true;
295: }
296:
297: catch (ConfigurationException ex) {
298: throw new CurnException(ex);
299: }
300: }
301:
302: /**
303: * Called immediately after a feed is parsed, but before it is
304: * otherwise processed. This method can return <tt>false</tt> to signal
305: * <i>curn</i> that the feed should be skipped. For instance, a plug-in
306: * that filters on the parsed feed data could use this method to weed
307: * out non-matching feeds before they are downloaded. Similarly, a
308: * plug-in that edits the parsed data (removing or editing individual
309: * items, for instance) could use method to do so.
310: *
311: * @param feedInfo the {@link FeedInfo} object for the feed that
312: * has been downloaded and parsed.
313: * @param feedCache the feed cache
314: * @param channel the parsed channel data
315: *
316: * @return <tt>true</tt> if <i>curn</i> should continue to process the
317: * feed, <tt>false</tt> to skip the feed. A return value of
318: * <tt>false</tt> aborts all further processing on the feed.
319: * In particular, <i>curn</i> will not pass the feed along to
320: * other plug-ins that have yet to be notified of this event.
321: *
322: * @throws CurnException on error
323: *
324: * @see RSSChannel
325: * @see FeedInfo
326: */
327: public boolean runPostFeedParsePlugIn(FeedInfo feedInfo,
328: FeedCache feedCache, RSSChannel channel)
329: throws CurnException {
330: URL feedURL = feedInfo.getURL();
331: log.debug("Post feed parse: " + feedURL.toString());
332:
333: Integer max = perFeedMaxArticlesMap.get(feedURL);
334: if (max == null)
335: max = defaultMaxArticlesToShow;
336:
337: if (max != null) {
338: Collection<RSSItem> items = channel.getItems();
339: int totalItems = items.size();
340: log.debug("Feed \"" + feedURL
341: + "\": Max articles for feed=" + max);
342: log.debug("Feed \"" + feedURL + "\": Total articles="
343: + totalItems);
344: if (totalItems > max) {
345: log.debug("Feed \"" + feedURL
346: + "\": Trimming articles.");
347: int i = 0;
348: Collection<RSSItem> trimmedItems = new ArrayList<RSSItem>();
349: for (RSSItem item : items) {
350: if (i++ >= max)
351: break;
352:
353: trimmedItems.add(item);
354: }
355:
356: channel.setItems(trimmedItems);
357: }
358: }
359:
360: return true;
361: }
362:
363: /*----------------------------------------------------------------------*\
364: Private Methods
365: \*----------------------------------------------------------------------*/
366: }
|