001: /**
002: * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the latest version of the GNU Lesser General
006: * Public License as published by the Free Software Foundation;
007: *
008: * This program is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
011: * GNU Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public License
014: * along with this program (LICENSE.txt); if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
016: */package org.jamwiki.servlets;
017:
018: import java.io.UnsupportedEncodingException;
019: import java.net.URLEncoder;
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.Iterator;
023: import java.util.List;
024: import javax.servlet.http.HttpServletRequest;
025: import javax.servlet.http.HttpServletResponse;
026: import com.sun.syndication.feed.synd.SyndContent;
027: import com.sun.syndication.feed.synd.SyndContentImpl;
028: import com.sun.syndication.feed.synd.SyndEntry;
029: import com.sun.syndication.feed.synd.SyndEntryImpl;
030: import com.sun.syndication.feed.synd.SyndFeed;
031: import com.sun.syndication.feed.synd.SyndFeedImpl;
032: import com.sun.syndication.io.SyndFeedOutput;
033: import org.apache.commons.lang.StringUtils;
034: import org.jamwiki.Environment;
035: import org.jamwiki.WikiBase;
036: import org.jamwiki.model.RecentChange;
037: import org.jamwiki.utils.Pagination;
038: import org.jamwiki.utils.Utilities;
039: import org.jamwiki.utils.WikiLogger;
040: import org.jamwiki.utils.WikiUtil;
041: import org.springframework.web.bind.ServletRequestUtils;
042: import org.springframework.web.servlet.ModelAndView;
043: import org.springframework.web.servlet.mvc.AbstractController;
044:
045: /**
046: * Provides an RSS or Atom feed for recent changes.
047: *
048: * Feed generation can be influenced through following request parameters:
049: * <ul>
050: * <li><code>feedType</code>: RSS or Atom (see
051: * {@link #setDefaultFeedType(String)}) </li>
052: * <li><code>minorEdits</code>: set to 'true' to include minor edits </li>
053: * <li><code>num</code>: number of entries to return (see
054: * {@link WikiUtil#buildPagination(HttpServletRequest)})</li>
055: * </ul>
056: *
057: * @author Rainer Schmitz
058: * @since 22.12.2006
059: */
060: public class RecentChangesFeedServlet extends AbstractController {
061:
062: private static final WikiLogger logger = WikiLogger
063: .getLogger(RecentChangesFeedServlet.class.getName());
064: private static final String MIME_TYPE = "application/xml";
065: private static final String FEED_ENCODING = "UTF-8";
066: private static final String DEFAULT_FEED_TYPE = "rss_2.0";
067: private static final String FEED_TYPE = "feedType";
068: private static final String MINOR_EDITS = "minorEdits";
069: private static final String LINK_TO_VERSION = "linkToVersion";
070: private String defaultFeedType = DEFAULT_FEED_TYPE;
071: private boolean defaultIncludeMinorEdits = false;
072: private boolean defaultLinkToVersion = false;
073: private String feedUrlPrefix = "";
074:
075: /**
076: * Sets the default feed type.
077: *
078: * Valid values are rss_0.92, rss_0.93, rss_0.94, rss_1.0, rss_2.0,
079: * atom_0.3, and atom_1.0.
080: *
081: * This value is used if no feed type is given in the request (parameter
082: * 'feedType'). Default is rss_2.0.
083: *
084: * @param defaultFeedType
085: * The defaultFeedType to set.
086: */
087: public void setDefaultFeedType(String defaultFeedType) {
088: this .defaultFeedType = defaultFeedType;
089: }
090:
091: /**
092: * Sets whether minor edits are included in generated feed.
093: *
094: * This value can be overriden by the request parameter 'minorEdits'.
095: * Default is false.
096: *
097: * @param includeMinorEdits
098: * <code>true</code> if minor edits shall be included in feed.
099: */
100: public void setDefaultIncludeMinorEdits(boolean includeMinorEdits) {
101: this .defaultIncludeMinorEdits = includeMinorEdits;
102: }
103:
104: /**
105: * Sets whether feed entry link should link to the changed or to the current
106: * version of the changed entry.
107: *
108: * This value can be overriden by the request parameter 'linkToVersion'.
109: * Default is false.
110: *
111: * @param feedUrlPrefix feed URL prefix to set; may not be null.
112: */
113: public void setFeedUrlPrefix(String feedUrlPrefix) {
114: if (feedUrlPrefix == null) {
115: throw new IllegalArgumentException(
116: "Feed URL prefix may not be null");
117: }
118: this .feedUrlPrefix = feedUrlPrefix;
119: }
120:
121: /**
122: * Prefix to use in feed and feed entry links.
123: *
124: * This is useful in portal environments to prefix the feed URL with the portal URL.
125: *
126: * @param linkToVersion
127: * <code>true</code> if link should point to edited version.
128: */
129: public void setDefaultLinkToVersion(boolean linkToVersion) {
130: this .defaultLinkToVersion = linkToVersion;
131: }
132:
133: /*
134: * (non-Javadoc)
135: *
136: * @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal(javax.servlet.http.HttpServletRequest,
137: * javax.servlet.http.HttpServletResponse)
138: */
139: protected ModelAndView handleRequestInternal(
140: HttpServletRequest request, HttpServletResponse response)
141: throws Exception {
142: try {
143: String feedType = ServletRequestUtils.getStringParameter(
144: request, FEED_TYPE, defaultFeedType);
145: logger.fine("Serving xml feed of type " + feedType);
146: SyndFeed feed = getFeed(request);
147: feed.setFeedType(feedType);
148: response.setContentType(MIME_TYPE);
149: response.setCharacterEncoding(FEED_ENCODING);
150: SyndFeedOutput output = new SyndFeedOutput();
151: output.output(feed, response.getWriter());
152: } catch (Exception e) {
153: logger.severe("Could not generate feed: " + e.getMessage(),
154: e);
155: response.sendError(
156: HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
157: "Could not generate feed: " + e.getMessage());
158: }
159: return null;
160: }
161:
162: /**
163: * TODO cache feed to avoid high load caused by RSS aggregators
164: *
165: * @throws Exception
166: */
167: private SyndFeed getFeed(HttpServletRequest request)
168: throws Exception {
169: Collection changes = getChanges(request);
170: SyndFeed feed = new SyndFeedImpl();
171: feed.setEncoding(FEED_ENCODING);
172: feed.setTitle(Environment.getValue(Environment.PROP_RSS_TITLE));
173: StringBuffer requestURL = request.getRequestURL();
174: String feedURL = feedUrlPrefix
175: + requestURL.substring(0, requestURL.length()
176: - WikiUtil.getTopicFromURI(request).length());
177: feed.setLink(feedURL);
178: feed.setDescription("List of the last " + changes.size()
179: + " changed wiki pages.");
180: boolean includeMinorEdits = ServletRequestUtils
181: .getBooleanParameter(request, MINOR_EDITS,
182: defaultIncludeMinorEdits);
183: boolean linkToVersion = ServletRequestUtils
184: .getBooleanParameter(request, LINK_TO_VERSION,
185: defaultLinkToVersion);
186: feed.setEntries(getFeedEntries(changes, includeMinorEdits,
187: linkToVersion, feedURL));
188: return feed;
189: }
190:
191: /**
192: *
193: */
194: private List getFeedEntries(Collection changes,
195: boolean includeMinorEdits, boolean linkToVersion,
196: String feedURL) {
197: List entries = new ArrayList();
198: for (Iterator iter = changes.iterator(); iter.hasNext();) {
199: RecentChange change = (RecentChange) iter.next();
200: if (includeMinorEdits || (!change.getMinor())) {
201: entries
202: .add(getFeedEntry(change, linkToVersion,
203: feedURL));
204: }
205: }
206: return entries;
207: }
208:
209: /**
210: *
211: */
212: private SyndEntry getFeedEntry(RecentChange change,
213: boolean linkToVersion, String feedURL) {
214: SyndContent description;
215: SyndEntry entry = new SyndEntryImpl();
216: entry.setTitle(change.getTopicName());
217: entry.setAuthor(change.getAuthorName());
218: entry.setPublishedDate(change.getEditDate());
219: description = new SyndContentImpl();
220: description.setType("text/plain");
221: StringBuffer descr = new StringBuffer();
222: if (!StringUtils.isBlank(change.getEditComment())) {
223: descr.append(change.getEditComment());
224: }
225: if (change.getDelete()) {
226: descr.append(" (deleted)");
227: } else {
228: if (linkToVersion) {
229: try {
230: String url = feedURL
231: + URLEncoder
232: .encode(
233: "Special:History?topicVersionId="
234: + change
235: .getTopicVersionId()
236: + "&topic="
237: + Utilities
238: .encodeForURL(change
239: .getTopicName()),
240: "UTF-8");
241: entry.setLink(url);
242: } catch (UnsupportedEncodingException e) {
243: // TODO Auto-generated catch block
244: e.printStackTrace();
245: }
246: } else {
247: entry
248: .setLink(feedURL
249: + Utilities.encodeForURL(change
250: .getTopicName()));
251: }
252: }
253: if (change.getUndelete()) {
254: descr.append(" (undeleted)");
255: }
256: if (change.getMinor()) {
257: descr.append(" (minor)");
258: }
259: description.setValue(descr.toString());
260: entry.setDescription(description);
261: // URI is used as GUID in RSS 2.0 and should therefore contain the
262: // version id
263: entry.setUri(feedURL
264: + Utilities.encodeForURL(change.getTopicName()) + "#"
265: + change.getTopicVersionId());
266: return entry;
267: }
268:
269: /**
270: *
271: */
272: private Collection getChanges(HttpServletRequest request)
273: throws Exception {
274: String virtualWiki = WikiUtil.getVirtualWikiFromURI(request);
275: Pagination pagination = WikiUtil.buildPagination(request);
276: return WikiBase.getDataHandler().getRecentChanges(virtualWiki,
277: pagination, true);
278: }
279:
280: }
|