/**
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the latest version of the GNU Lesser General
* Public License as published by the Free Software Foundation;
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program (LICENSE.txt); if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.jamwiki.servlets;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.SyndFeedOutput;
import org.apache.commons.lang.StringUtils;
import org.jamwiki.Environment;
import org.jamwiki.WikiBase;
import org.jamwiki.model.RecentChange;
import org.jamwiki.utils.Pagination;
import org.jamwiki.utils.Utilities;
import org.jamwiki.utils.WikiLogger;
import org.jamwiki.utils.WikiUtil;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
/**
* Provides an RSS or Atom feed for recent changes.
*
* Feed generation can be influenced through following request parameters:
* <ul>
* <li><code>feedType</code>: RSS or Atom (see
* {@link #setDefaultFeedType(String)}) </li>
* <li><code>minorEdits</code>: set to 'true' to include minor edits </li>
* <li><code>num</code>: number of entries to return (see
* {@link WikiUtil#buildPagination(HttpServletRequest)})</li>
* </ul>
*
* @author Rainer Schmitz
* @since 22.12.2006
*/
public class RecentChangesFeedServlet extends AbstractController {
private static final WikiLogger logger = WikiLogger.getLogger(RecentChangesFeedServlet.class.getName());
private static final String MIME_TYPE = "application/xml";
private static final String FEED_ENCODING = "UTF-8";
private static final String DEFAULT_FEED_TYPE = "rss_2.0";
private static final String FEED_TYPE = "feedType";
private static final String MINOR_EDITS = "minorEdits";
private static final String LINK_TO_VERSION = "linkToVersion";
private String defaultFeedType = DEFAULT_FEED_TYPE;
private boolean defaultIncludeMinorEdits = false;
private boolean defaultLinkToVersion = false;
private String feedUrlPrefix = "";
/**
* Sets the default feed type.
*
* Valid values are rss_0.92, rss_0.93, rss_0.94, rss_1.0, rss_2.0,
* atom_0.3, and atom_1.0.
*
* This value is used if no feed type is given in the request (parameter
* 'feedType'). Default is rss_2.0.
*
* @param defaultFeedType
* The defaultFeedType to set.
*/
public void setDefaultFeedType(String defaultFeedType) {
this.defaultFeedType = defaultFeedType;
}
/**
* Sets whether minor edits are included in generated feed.
*
* This value can be overriden by the request parameter 'minorEdits'.
* Default is false.
*
* @param includeMinorEdits
* <code>true</code> if minor edits shall be included in feed.
*/
public void setDefaultIncludeMinorEdits(boolean includeMinorEdits) {
this.defaultIncludeMinorEdits = includeMinorEdits;
}
/**
* Sets whether feed entry link should link to the changed or to the current
* version of the changed entry.
*
* This value can be overriden by the request parameter 'linkToVersion'.
* Default is false.
*
* @param feedUrlPrefix feed URL prefix to set; may not be null.
*/
public void setFeedUrlPrefix(String feedUrlPrefix) {
if (feedUrlPrefix == null) {
throw new IllegalArgumentException("Feed URL prefix may not be null");
}
this.feedUrlPrefix = feedUrlPrefix;
}
/**
* Prefix to use in feed and feed entry links.
*
* This is useful in portal environments to prefix the feed URL with the portal URL.
*
* @param linkToVersion
* <code>true</code> if link should point to edited version.
*/
public void setDefaultLinkToVersion(boolean linkToVersion) {
this.defaultLinkToVersion = linkToVersion;
}
/**
* Handle the servlet request, generating appropriate output.
*
* @see org.springframework.web.servlet.mvc.AbstractController#handleRequestInternal(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
String feedType = ServletRequestUtils.getStringParameter(request, FEED_TYPE, defaultFeedType);
logger.finer("Serving xml feed of type " + feedType);
SyndFeed feed = getFeed(request);
feed.setFeedType(feedType);
response.setContentType(MIME_TYPE);
response.setCharacterEncoding(FEED_ENCODING);
SyndFeedOutput output = new SyndFeedOutput();
output.output(feed, response.getWriter());
} catch (Exception e) {
logger.severe("Could not generate feed: " + e.getMessage(), e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not generate feed: "
+ e.getMessage());
}
return null;
}
/**
* TODO cache feed to avoid high load caused by RSS aggregators
*
* @throws Exception
*/
private SyndFeed getFeed(HttpServletRequest request) throws Exception {
List<RecentChange> changes = getChanges(request);
SyndFeed feed = new SyndFeedImpl();
feed.setEncoding(FEED_ENCODING);
feed.setTitle(Environment.getValue(Environment.PROP_RSS_TITLE));
StringBuffer requestURL = request.getRequestURL();
String feedURL = feedUrlPrefix + requestURL.substring(0, requestURL.length() - WikiUtil.getTopicFromURI(request).length());
feed.setLink(feedURL);
feed.setDescription("List of the last " + changes.size() + " changed wiki pages.");
boolean includeMinorEdits = ServletRequestUtils.getBooleanParameter(request, MINOR_EDITS,
defaultIncludeMinorEdits);
boolean linkToVersion = ServletRequestUtils.getBooleanParameter(request, LINK_TO_VERSION, defaultLinkToVersion);
feed.setEntries(getFeedEntries(changes, includeMinorEdits, linkToVersion, feedURL));
return feed;
}
/**
*
*/
private List<SyndEntry> getFeedEntries(List<RecentChange> changes, boolean includeMinorEdits, boolean linkToVersion, String feedURL) {
List<SyndEntry> entries = new ArrayList<SyndEntry>();
for (RecentChange change : changes) {
// FIXME - add support for log item changes
if (!StringUtils.isBlank(change.getTopicName()) && (includeMinorEdits || !change.getMinor())) {
entries.add(getFeedEntry(change, linkToVersion, feedURL));
}
}
return entries;
}
/**
*
*/
private SyndEntry getFeedEntry(RecentChange change, boolean linkToVersion, String feedURL) {
SyndContent description;
SyndEntry entry = new SyndEntryImpl();
entry.setTitle(change.getTopicName());
entry.setAuthor(change.getAuthorName());
entry.setPublishedDate(change.getChangeDate());
description = new SyndContentImpl();
description.setType("text/plain");
StringBuffer descr = new StringBuffer();
if (!StringUtils.isBlank(change.getChangeComment())) {
descr.append(change.getChangeComment());
}
if (change.isDelete()) {
descr.append(" (deleted)");
} else {
if (linkToVersion) {
try {
String url = feedURL + URLEncoder.encode("Special:History?topicVersionId=" + change.getTopicVersionId() + "&topic="
+ Utilities.encodeAndEscapeTopicName(change.getTopicName()), "UTF-8");
entry.setLink(url);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
entry.setLink(feedURL + Utilities.encodeAndEscapeTopicName(change.getTopicName()));
}
}
if (change.isUndelete()) {
descr.append(" (undeleted)");
}
if (change.getMinor()) {
descr.append(" (minor)");
}
description.setValue(descr.toString());
entry.setDescription(description);
// URI is used as GUID in RSS 2.0 and should therefore contain the
// version id
entry.setUri(feedURL + Utilities.encodeAndEscapeTopicName(change.getTopicName()) + "#" + change.getTopicVersionId());
return entry;
}
/**
*
*/
private List<RecentChange> getChanges(HttpServletRequest request) throws Exception {
String virtualWiki = WikiUtil.getVirtualWikiFromURI(request);
Pagination pagination = WikiUtil.buildPagination(request);
return WikiBase.getDataHandler().getRecentChanges(virtualWiki, pagination, true);
}
}