/*
* Copyright 2010 Jasha Joachimsthal
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onehippo.forge.weblogdemo.components;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import com.sun.syndication.feed.synd.SyndCategory;
import com.sun.syndication.feed.synd.SyndCategoryImpl;
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.FeedException;
import com.sun.syndication.io.SyndFeedOutput;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.hippoecm.hst.content.beans.query.HstQuery;
import org.hippoecm.hst.content.beans.query.HstQueryResult;
import org.hippoecm.hst.content.beans.query.exceptions.QueryException;
import org.hippoecm.hst.content.beans.standard.HippoBean;
import org.hippoecm.hst.content.beans.standard.HippoBeanIterator;
import org.hippoecm.hst.content.beans.standard.HippoDocumentIterator;
import org.hippoecm.hst.content.beans.standard.HippoFacetChildNavigationBean;
import org.hippoecm.hst.content.beans.standard.HippoHtml;
import org.hippoecm.hst.core.component.HstComponentException;
import org.hippoecm.hst.core.component.HstRequest;
import org.hippoecm.hst.core.component.HstResponse;
import org.hippoecm.hst.core.linking.HstLink;
import org.hippoecm.hst.core.request.ResolvedSiteMapItem;
import org.onehippo.forge.weblogdemo.beans.BeanConstants;
import org.onehippo.forge.weblogdemo.beans.Blogpost;
import org.onehippo.forge.weblogdemo.components.overview.BlogListing;
import org.onehippo.forge.weblogdemo.hstextensions.ContentRewriterImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Creates Atom or RSS feed using ROME. Needs a JSP that only sets the mime-type
* Example used from http://wiki.java.net/bin/view/Javawsxml/Rome04TutorialFeedWriter
*
* @author Jasha Joachimsthal
*/
public class FeedCreator extends BaseSiteComponent {
private static final String PARAM_PREFERRED_PATH = "preferredPath";
private static final String PARAM_DESCRIPTION = "description";
private static final String PARAM_COPYRIGHT = "copyright";
private static final String PARAM_AUTHOR = "author";
private static final String PARAM_TITLE = "title";
private static final String DEFAULT_FEED_DESCRIPTION = "Feed description";
private static final String DEFAULT_FEED_TITLE = "Feed title";
private static final String FEEDTYPE_PARAM = "alt";
private static final String RSS_VALUE = "rss";
private static final String RSS_RENDERPATH = "/WEB-INF/jsp/feeds/rss.jsp";
private static final String DEFAULT_FEED_TYPE = "atom_1.0";
private static final String RSS_FEED_TYPE = "rss_2.0";
private static final String APPLICATION_XHTML_XML = "application/xhtml+xml";
private static final String XHTML_TAG_DIV_START = "<div xmlns=\"http://www.w3.org/1999/xhtml\">";
private static final String HTML_TAG_DIV_END = "</div>";
private static final String HTML_TAG_P_START = "<p>";
private static final String HTML_TAG_P_END = "</p>";
public static final Logger log = LoggerFactory.getLogger(FeedCreator.class);
@Override
public void doBeforeRender(HstRequest request, HstResponse response) throws HstComponentException {
super.doBeforeRender(request, response);
HippoBean hippoBean = getContentBean(request);
final String authorName = getParameter(PARAM_AUTHOR, request);
SyndFeed feed = new SyndFeedImpl();
setFeedMetadata(request, response, hippoBean, feed, authorName);
setFeedEntries(request, response, hippoBean, feed, authorName);
SyndFeedOutput output = new SyndFeedOutput();
try {
output.output(feed, response.getWriter(), true);
} catch (IOException e) {
throw new HstComponentException("Error writing feed to Response", e);
} catch (FeedException e) {
throw new HstComponentException("Error writing feed", e);
}
if (RSS_FEED_TYPE.equals(feed.getFeedType())) {
response.setRenderPath(RSS_RENDERPATH);
}
}
/**
* Sets the List of {@link SyndEntry} to the feed
*
* @param request current {@link HstRequest}
* @param response {@link HstResponse}
* @param hippoBean {@link HippoBean} for the current request
* @param feed {@link SyndFeed}
* @param authorName {@link String} with the name of the author
*/
@SuppressWarnings("unchecked")
protected void setFeedEntries(HstRequest request, HstResponse response, HippoBean hippoBean, SyndFeed feed,
final String authorName) {
List<SyndEntry> entries = new ArrayList<SyndEntry>();
if (hippoBean instanceof HippoFacetChildNavigationBean) {
HippoFacetChildNavigationBean facetNav = (HippoFacetChildNavigationBean) hippoBean;
HippoDocumentIterator<Blogpost> it = facetNav.getResultSet().getDocumentIterator(Blogpost.class);
handleEntries(entries, it, request, response, authorName);
} else {
try {
HstQuery hstQuery = getQueryManager().createQuery(hippoBean, Blogpost.class);
hstQuery.addOrderByDescending(BeanConstants.PROP_DATE);
HstQueryResult result = hstQuery.execute();
HippoBeanIterator beans = result.getHippoBeans();
handleEntries(entries, beans, request, response, authorName);
} catch (QueryException e) {
log.warn("Error in querying blogposts, no entries for the feed", e);
}
}
feed.setEntries(entries);
}
/**
* Sets all the metadata for the {@link SyndFeed} (title, author, description etc)
*
* @param request current {@link HstRequest}
* @param response {@link HstResponse}
* @param hippoBean {@link HippoBean} for the current request
* @param feed {@link SyndFeed} to set the metadata
* @param authorName Author of the feed
*/
protected void setFeedMetadata(HstRequest request, HstResponse response, HippoBean hippoBean, SyndFeed feed,
final String authorName) {
String feedType = getPublicRequestParameter(request, FEEDTYPE_PARAM);
feed.setFeedType(RSS_VALUE.equals(feedType) ? RSS_FEED_TYPE : DEFAULT_FEED_TYPE);
String title = getParameter(PARAM_TITLE, request);
feed.setTitle(StringUtils.isBlank(title) ? DEFAULT_FEED_TITLE : title);
if (StringUtils.isNotBlank(authorName)) {
feed.setAuthor(authorName);
}
String copyright = getParameter(PARAM_COPYRIGHT, request);
if (StringUtils.isNotBlank(copyright)) {
feed.setCopyright(copyright);
}
String description = getParameter(PARAM_DESCRIPTION, request);
feed.setDescription(StringUtils.isBlank(description) ? DEFAULT_FEED_DESCRIPTION : description);
feed.setPublishedDate(new Date());
ResolvedSiteMapItem currentSiteMapItem = request.getRequestContext().getResolvedSiteMapItem();
setFeedLinkInformation(request, response, hippoBean, feed, currentSiteMapItem);
}
/**
* Sets both the link to the corresponding webpage and the URI of the feed.
*
* @param request current {@link org.hippoecm.hst.core.component.HstRequest}
* @param response {@link org.hippoecm.hst.core.component.HstResponse}
* @param hippoBean {@link org.hippoecm.hst.content.beans.standard.HippoBean} for the current request
* @param feed {@link com.sun.syndication.feed.synd.SyndFeed} that is being creted for this request
* @param currentSiteMapItem {@link org.hippoecm.hst.core.request.ResolvedSiteMapItem} for the current request
*/
private void setFeedLinkInformation(HstRequest request, HstResponse response, HippoBean hippoBean, SyndFeed feed,
ResolvedSiteMapItem currentSiteMapItem) {
final HstLink feedLink;
String preferredPath = getParameter(PARAM_PREFERRED_PATH, request);
if (StringUtils.isBlank(preferredPath)) {
feedLink = request.getRequestContext().getHstLinkCreator()
.createCanonical(hippoBean.getNode(), currentSiteMapItem);
} else {
ResolvedSiteMapItem preferredItem = request.getRequestContext().getSiteMapMatcher()
.match(preferredPath, request.getRequestContext().getResolvedMount());
feedLink = request.getRequestContext().getHstLinkCreator()
.createCanonical(hippoBean.getNode(), currentSiteMapItem, preferredItem.getHstSiteMapItem());
}
feed.setLink(feedLink.toUrlForm(request, response, true));
feed.setUri(feedLink.toUrlForm(request, response, true));
}
/**
* For each {@link Blogpost} it creates a {@link SyndEntry} that is displayed in the
* {@link com.sun.syndication.feed.atom.Feed}
*
* @param entries {@link List}
* @param beans {@link Iterator} of {@link Blogpost}
* @param request {@link HstRequest}
* @param response {@link HstResponse}
* @param author {@link String} with the name of the blog author
*/
private void handleEntries(List<SyndEntry> entries, @SuppressWarnings("rawtypes") Iterator beans,
HstRequest request, HstResponse response, final String author) {
int results = 0;
Blogpost blogpost;
while (beans.hasNext() && results < BlogListing.PAGESIZE) {
blogpost = (Blogpost) beans.next();
SyndEntry syndEntry = createFeedEntryFromBlogpost(request, response, author, blogpost);
if (syndEntry != null) {
entries.add(syndEntry);
results++;
}
}
}
/**
* Creates a {@link SyndEntry} from a {@link Blogpost}
*
* @param request current {@link HstRequest}
* @param response {@link HstResponse}
* @param author Author of the blog
* @param blogpost {@link Blogpost} that contains the content for the {@link SyndEntry}
* @return a {@link SyndEntry} or {@code null} if the {@link Blogpost} is {@code null}
*/
private SyndEntry createFeedEntryFromBlogpost(HstRequest request, HstResponse response, final String author,
Blogpost blogpost) {
if (blogpost == null) {
return null;
}
SyndEntry entry = new SyndEntryImpl();
entry.setTitle(blogpost.getRawTitle());
final HstLink link = request.getRequestContext().getHstLinkCreator()
.create(blogpost, request.getRequestContext());
entry.setLink(link.toUrlForm(request, response, true));
// blogger id's are from the migrated Blogger.com Blogposts. The URI is maintained so external sources don't see them as new.
entry.setUri(StringUtils.isBlank(blogpost.getBloggerId()) ? link.toUrlForm(request, response, true) : blogpost
.getBloggerId());
entry.setPublishedDate(blogpost.getDate());
if (StringUtils.isNotBlank(author)) {
entry.setAuthor(author);
}
setEntryCategories(blogpost, entry);
SyndContent description = new SyndContentImpl();
description.setType(APPLICATION_XHTML_XML);
description.setValue(getEntryDescription(blogpost, request, response));
entry.setDescription(description);
return entry;
}
/**
* Sets a {@link List} of {@link SyndCategory} element based on the assigned tags in the {@link Blogpost}
*
* @param blogpost {@link Blogpost} with tags
* @param entry {@link SyndEntry}
*/
private void setEntryCategories(Blogpost blogpost, SyndEntry entry) {
List<SyndCategory> categories = new ArrayList<SyndCategory>();
for (String tag : blogpost.getTags()) {
SyndCategory category = new SyndCategoryImpl();
category.setName(tag);
categories.add(category);
}
entry.setCategories(categories);
}
/**
* Returns a String the HTML content of a {@link Blogpost}
*
* @param blogpost {@link Blogpost}
* @param request {@link HstRequest}
* @param response {@link HstResponse}
* @return String of the HTML content, {@code null} if absent or empty
*/
private String getEntryDescription(Blogpost blogpost, HstRequest request, HstResponse response) {
StringBuffer fullContent = new StringBuffer(XHTML_TAG_DIV_START);
if (StringUtils.isNotBlank(blogpost.getSummary())) {
fullContent.append(HTML_TAG_P_START);
fullContent.append(StringEscapeUtils.escapeXml(blogpost.getSummary()));
fullContent.append(HTML_TAG_P_END);
}
fullContent.append(getDescriptionHtmlContent(blogpost, request, response));
fullContent.append(HTML_TAG_DIV_END);
return fullContent.toString();
}
/**
* Creates the rewritten HTML content for the description field of a {@link SyndEntry}. Returns an empty {@link String}
* if there is no HTML content.
*
* @param blogpost {@link Blogpost} with the HTML content
* @param request current {@link HstRequest}
* @param response {@link HstResponse}
* @return HTML content of the description field with all internal links rewritten as external URLs
*/
private String getDescriptionHtmlContent(Blogpost blogpost, HstRequest request, HstResponse response) {
HippoHtml hippoHtml = blogpost.getHtml();
if (hippoHtml == null || hippoHtml.getNode() == null) {
if (log.isDebugEnabled()) {
log.debug(blogpost.getName() + " has no HTML node");
}
return StringUtils.EMPTY;
}
String htmlContent = blogpost.getHtml().getContent();
if (StringUtils.isBlank(htmlContent)) {
if (log.isDebugEnabled()) {
log.debug(blogpost.getName() + " has no or empty HTML content");
}
return StringUtils.EMPTY;
}
ContentRewriterImpl contentRewriter = new ContentRewriterImpl();
return contentRewriter.rewriteToExternal(htmlContent, hippoHtml.getNode(), request.getRequestContext());
}
}