ForumFeedGenerator.java
/*
* Copyright (C) 2003-2009 eXo Platform SAS.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either version 3
* 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, see<http://www.gnu.org/licenses/>.
*/
package org.exoplatform.forum.rss;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.exoplatform.forum.common.jcr.JCRTask;
import org.exoplatform.forum.common.jcr.KSDataLocation;
import org.exoplatform.forum.common.jcr.PropertyReader;
import org.exoplatform.forum.common.jcr.VoidReturn;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
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.SyndFeed;
/**
* @author <a href="mailto:patrice.lamarque@exoplatform.com">Patrice Lamarque</a>
* @version $Revision$
*/
public final class ForumFeedGenerator extends RSSProcess implements FeedContentProvider, FeedListener {
private static final Log LOG = ExoLogger.getLogger(ForumFeedGenerator.class);
public static final String FORUM_RSS_TYPE = "exo:forumRSS".intern();
public static final String KS_FORUM = "forum".intern();
public static final String FORUM_APP = "ForumService".intern();
public ForumFeedGenerator(KSDataLocation locator) {
super(locator);
}
public ForumFeedGenerator() {
super();
}
public void itemAdded(String path) {
try {
ItemSavedTask task = new ItemSavedTask(path,false);
dataLocator.getSessionManager().executeAndSave(task);
} catch (Exception e) {
LOG.error("itemAdded failed for " + path, e);
}
}
public void itemUpdated(String path) {
try {
ItemSavedTask task = new ItemSavedTask(path,true);
dataLocator.getSessionManager().executeAndSave(task);
} catch (Exception e) {
LOG.error("itemUpdated failed for" + path, e);
}
}
public void itemRemoved(String path) {
try {
ItemRemovedTask task = new ItemRemovedTask(path);
dataLocator.getSessionManager().executeAndSave(task);
} catch (Exception e) {
LOG.error("itemRemoved failed for " + path, e);
}
}
class ItemSavedTask implements JCRTask<VoidReturn> {
private String path;
private boolean updated;
public ItemSavedTask(String path, boolean updated) {
this.path = path;
this.updated = updated;
}
public VoidReturn execute(Session session) throws Exception {
Node node;
node = (Node)getCurrentSession().getItem(path);
String linkItem = getPageLink() + "?portal:componentId=forum&portal:type=action&portal:isSecure=false&uicomponent=UIBreadcumbs&"
+ "op=ChangePath&objectId=";
if(node.isNodeType("exo:post")){
linkItem = node.getProperty("exo:link").getString();
linkItem = linkItem.substring(0, linkItem.indexOf("objectId=")+9);
postUpdated(path, linkItem, updated);
} else if (node.isNodeType("exo:topic")) {
topicUpdated(path, linkItem, updated);
} else if (node.isNodeType("exo:forum")) {
forumUpdated(path, linkItem, updated);
}
return VoidReturn.VALUE;
}
}
class ItemRemovedTask implements JCRTask<VoidReturn> {
private String path;
public ItemRemovedTask(String path) {
this.path = path;
}
public VoidReturn execute(Session session) throws Exception {
String objectId = null;
String description = null;
objectId = path.substring(path.lastIndexOf("/") + 1);
Node node = null;
if(objectId.contains("post")){
while(node == null){
try{
node = (Node)getCurrentSession().getItem(path);
}catch(PathNotFoundException pn){
path = path.substring(0, path.lastIndexOf("/"));
node = null;
}
}
while(node.isNodeType("exo:forumCategory") || node.isNodeType("exo:forum") || node.isNodeType("exo:topic")){
description = new PropertyReader(node).string("exo:description", " ");
removeItemInFeed(objectId, node, description);
node = node.getParent();
}
} else {
path = path.substring(0, path.lastIndexOf("/"));
while(node == null){
try{
node = (Node)getCurrentSession().getItem(path);
}catch(PathNotFoundException pn){
objectId = path.substring(path.lastIndexOf("/") + 1);
path = path.substring(0, path.lastIndexOf("/"));
node = null;
}
}
while(node.isNodeType("exo:forumCategory") || node.isNodeType("exo:forum") || node.isNodeType("exo:topic")){
description = new PropertyReader(node).string("exo:description", " ");
if(node.isNodeType("exo:forum") || node.isNodeType("exo:forumCategory")) {
removeRSSItem(objectId, node, description);
} else {
removeItemInFeed(objectId, node, description);
}
node = node.getParent();
}
}
return VoidReturn.VALUE;
}
}
/**
* Remove one item from RSS feed based on id of object which is changed
* @param itemId id of object
* @param node Node content RSS feed
* @param feedDescription description about RSS feed
* @throws Exception
*/
protected void removeItemInFeed(String itemId, Node node, String feedDescription) throws Exception{
RSS data = new RSS(node);
SyndFeed feed = data.removeEntry(itemId);
String title = new PropertyReader(node).string("exo:name", "Root");
feed.setTitle(title);
feed.setDescription(feedDescription);
data.saveFeed(feed, FORUM_RSS_TYPE);
}
@SuppressWarnings("unchecked")
protected void removeRSSItem(String itemId, Node node, String description) throws Exception {
RSS data = new RSS(node);
SyndFeed feed = data.read();
List<SyndEntry> entries = feed.getEntries();
Node removeNode = getNodeById(itemId);
if (removeNode.isNodeType("exo:topic")){
List<Node> listRemovePosts = getListRemove(removeNode,"exo:post");
removeItem(entries ,listRemovePosts);
} else if (removeNode.isNodeType("exo:forum")){
List<Node> listRemoveForum = getListRemove(removeNode,"exo:topic");
for (Node n : listRemoveForum) {
List<Node> listRemovePosts = getListRemove(n,"exo:post");
removeItem(entries ,listRemovePosts);
}
removeItem(entries ,listRemoveForum);
}
feed.setEntries(entries);
String title = new PropertyReader(node).string("exo:name", "Root");
feed.setTitle(title);
feed.setDescription(description);
data.saveFeed(feed, FORUM_RSS_TYPE);
}
protected void removeItem(List<SyndEntry> entries,List<Node> listRemove) throws RepositoryException{
List<SyndEntry> entries1 = new ArrayList<SyndEntry>();
boolean flag = true;
for(SyndEntry syndEntry : entries){
flag = true;
for(Node post : listRemove){
if(syndEntry.getUri().equals(post.getName())){
flag = false;
break;
}
}
if(flag){
entries1.add(syndEntry);
}
}
entries.clear();
entries.addAll(entries1);
}
protected List<Node> getListRemove(Node removeNode, String childNodeType) throws RepositoryException{
List<Node> listRemove = new ArrayList<Node>();
NodeIterator nodeIterator = removeNode.getNodes();
Node nodePost = null;
while(nodeIterator.hasNext()){
nodePost = nodeIterator.nextNode();
if(nodePost.isNodeType(childNodeType)){
listRemove.add(nodePost);
}
}
return listRemove;
}
protected void topicUpdated(String path, String linkItem, final boolean updated){
try {
Node topicNode = (Node)getCurrentSession().getItem(path);
Node forumNode = topicNode.getParent();
Node categoryNode = forumNode.getParent();
boolean categoryHasRestrictedAudience = (hasProperty(categoryNode, "exo:viewer"));
boolean forumHasRestrictedAudience = (hasProperty(forumNode, "exo:viewer"));
boolean debug = LOG.isDebugEnabled();
String topicName = topicNode.getName() ;
if (categoryHasRestrictedAudience || forumHasRestrictedAudience) {
if (debug) {
LOG.debug("Post" + topicName +" was not added to feed because category or forum has restricted audience");
}
return;
}
PropertyReader topic = new PropertyReader(topicNode);
SyndContent description;
List<String> listContent = new ArrayList<String>();
String message = topic.string("exo:description");
listContent.add(message);
description = new SyndContentImpl();
description.setType(RSS.PLAIN_TEXT);
description.setValue(message);
final String title = topic.string("exo:name");
final Date created = topic.date("exo:createdDate");
final String owner = topic.string("exo:owner");
SyndEntry entry = RSS.createNewEntry(topicName, title, linkItem, listContent, description,created , owner);
entry.setLink(linkItem + topicNode.getName());
// save topic, forum and category feeds
for(Node node : new Node[]{forumNode, categoryNode}) {
PropertyReader reader = new PropertyReader(node);
String desc = reader.string("exo:description", " ");
RSS data = new RSS(node);
if (data.feedExists()) {
SyndFeed feed = null;
if (updated) {
feed = data.removeEntry(topicName);
}
feed = data.addEntry(entry);
feed.setDescription(reader.string("exo:description", " "));
data.saveFeed(feed, FORUM_RSS_TYPE);
} else {
SyndFeed feed = RSS.createNewFeed(node.getProperty("exo:name").getString(), node.getProperty("exo:createdDate").getDate().getTime());
feed.setLink(linkItem + node.getName());
feed.setEntries(Arrays.asList(new SyndEntry[]{entry}));
feed.setDescription(desc);
data.saveFeed(feed, FORUM_RSS_TYPE);
}
}
//update posts in topic
NodeIterator nodeIterator = topicNode.getNodes();
Node postNode = null;
while(nodeIterator.hasNext()){
postNode = nodeIterator.nextNode();
if(postNode.isNodeType("exo:post")){
try {
postUpdated(postNode, linkItem, updated);
} catch (Exception e) {
if(LOG.isDebugEnabled()) {
LOG.debug("Failed to generate feed for post " + postNode.getPath(), e);
}
}
}
}
} catch (PathNotFoundException e) {
LOG.warn("Failed to get properties", e);
} catch (Exception e) {
LOG.error("Failed to update topics", e);
}
}
protected void forumUpdated(String path, String linkItem, final boolean updated){
try {
Node forumNode = (Node)getCurrentSession().getItem(path);
NodeIterator nodeIterator = forumNode.getNodes();
Node topicNode = null;
while(nodeIterator.hasNext()){
topicNode = nodeIterator.nextNode();
if(topicNode.isNodeType("exo:topic")){
topicUpdated(topicNode.getPath(), linkItem, updated);
}
}
} catch (PathNotFoundException e) {
LOG.warn("Failed to get item with path " + path, e);
} catch (RepositoryException e) {
LOG.warn("Failed to get item with path " + path, e);
}
}
private void postUpdated(Node postNode, String linkItem, final boolean updated) throws Exception {
boolean debug = LOG.isDebugEnabled();
Node topicNode = postNode.getParent();
String postName = postNode.getName();
PropertyReader post = new PropertyReader(postNode);
PropertyReader topic = new PropertyReader(topicNode);
boolean isFirstPost = post.bool("exo:isFirstPost");//postNode.hasProperty("exo:isFirstPost") && postNode.getProperty("exo:isFirstPost").getBoolean();
boolean notApproved = !post.bool("exo:isApproved");//(postNode.hasProperty("exo:isApproved") && !postNode.getProperty("exo:isApproved").getBoolean());
boolean isPrivatePost = hasProperty(topicNode, "exo:userPrivate") && !"exoUserPri".equals(topic.strings("exo:userPrivate")[0]); //(postNode.hasProperty("exo:userPrivate") && !postNode.getProperty("exo:userPrivate").getValues()[0].getString().equals("exoUserPri"));
boolean topicHasLimitedViewers = hasProperty(topicNode, "exo:canView"); //(topicNode.hasProperty("exo:canView") && topicNode.getProperty("exo:canView").getValues()[0].getString().trim().length() > 0);
if ((isFirstPost && notApproved) || isPrivatePost || topicHasLimitedViewers) {
if (debug) {
LOG.debug("Post" + postName +" was not added to feed because it is private or topic has restricted audience or it is approval pending");
}
return;
}
Node forumNode = topicNode.getParent();
Node categoryNode = forumNode.getParent();
boolean categoryHasRestrictedAudience = (hasProperty(categoryNode, "exo:viewer"));
boolean forumHasRestrictedAudience = (hasProperty(forumNode, "exo:viewer"));
if (categoryHasRestrictedAudience || forumHasRestrictedAudience) {
if (debug) {
LOG.debug("Post" + postName +" was not added to feed because category or forum has restricted audience");
}
return;
}
boolean inactive = !post.bool("exo:isActiveByTopic");//(postNode.hasProperty("exo:isActiveByTopic") && !postNode.getProperty("exo:isActiveByTopic").getBoolean());
boolean hidden = post.bool("exo:isHidden");//(postNode.hasProperty("exo:isHidden") && postNode.getProperty("exo:isHidden").getBoolean());
if(notApproved || inactive || hidden) {
if (updated) {
removePostFromParentFeeds(postNode, topicNode, forumNode, categoryNode);
}
if (debug) {
LOG.debug("Post" + postName +" was not added to feed because because it is hidden, inactive or not approved");
}
return;
}
SyndContent description;
List<String> listContent = new ArrayList<String>();
String message = post.string("exo:message");
listContent.add(message);
description = new SyndContentImpl();
description.setType(RSS.PLAIN_TEXT);
description.setValue(message);
final String title = post.string("exo:name");
final Date created = post.date("exo:createdDate");
final String owner = post.string("exo:owner");
SyndEntry entry = RSS.createNewEntry(postName, title, linkItem, listContent, description,created , owner);
entry.setLink(linkItem + topicNode.getName());
// save topic, forum and category feeds
for(Node node : new Node[]{topicNode, forumNode, categoryNode}) {
PropertyReader reader = new PropertyReader(node);
String desc = reader.string("exo:description", " ");
RSS data = new RSS(node);
if (data.feedExists()) {
SyndFeed feed = null;
if (updated) {
feed = data.removeEntry(postName);
}
feed = data.addEntry(entry);
feed.setDescription(reader.string("exo:description", " "));
data.saveFeed(feed, FORUM_RSS_TYPE);
} else {
SyndFeed feed = RSS.createNewFeed(title, created);
feed.setLink(linkItem + node.getName());
feed.setEntries(Arrays.asList(new SyndEntry[]{entry}));
feed.setDescription(desc);
data.saveFeed(feed, FORUM_RSS_TYPE);
}
}
}
protected void postUpdated(String path, String linkItem, final boolean updated){
try{
Node postNode = (Node)getCurrentSession().getItem(path);
postUpdated(postNode, linkItem, updated);
}catch(Exception e){
LOG.error("Failed to generate feed for post" + path, e);
}
}
private void removePostFromParentFeeds(Node postNode, Node topicNode, Node forumNode, Node categoryNode) throws Exception {
for(Node node : new Node[]{topicNode, forumNode, categoryNode}){
String description = new PropertyReader(node).string("exo:description", " ");
RSS data = new RSS(node);
SyndFeed feed = data.removeEntry(postNode.getName());
String title = new PropertyReader(node).string("exo:name", "Root");
feed.setTitle(title);
feed.setDescription(description);
data.saveFeed(feed, FORUM_RSS_TYPE);
}
}
private boolean hasProperty(Node node, String property) throws Exception {
if(node.hasProperty(property) && node.getProperty(property).getValues().length > 0 && node.getProperty(property).getValues()[0].getString().trim().length() > 0)
return true;
else return false;
}
public InputStream getFeedContent(String targetId) {
return dataLocator.getSessionManager().executeAndSave(new GetFeedStreamTask(targetId));
}
class GetFeedStreamTask implements JCRTask<InputStream> {
private String targetId;
public GetFeedStreamTask(String targetId) {
this.targetId = targetId;
}
public InputStream execute(Session session) throws Exception {
Node parentNode = getNodeById(targetId);
return getFeedStream(parentNode, FORUM_RSS_TYPE, "FORUM RSS FEED");
}
}
}