/**
* Copyright 2010 Daniel Guermeur and Amy Unruh
*
* 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.
*
* See http://connectrapp.appspot.com/ for a demo, and links to more information
* about this app and the book that it accompanies.
*/
package com.metadot.book.connectr.server.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.jdo.PersistenceManager;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import org.apache.commons.lang.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;
import com.google.appengine.api.datastore.Text;
import com.metadot.book.connectr.server.utils.cache.CacheSupport;
import com.metadot.book.connectr.server.utils.cache.Cacheable;
import com.metadot.book.connectr.shared.StreamItemDTO;
import com.metadot.book.connectr.shared.StreamItemSummaryDTO;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndImage;
import com.sun.syndication.feed.synd.SyndLink;
import com.sun.syndication.feed.synd.SyndPerson;
/**
* The StreamItem persistence-capable class holds information about feed items.
* Objects from this class form the basis of the 'activity stream' display.
*/
@SuppressWarnings("serial")
@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class StreamItem implements Serializable, Cacheable {
private static final int CACHE_EXPIR = 1440 * 60; // expire the stream items after 24 hours, in seconds
private static final int SUMMARY_LENGTH = 246; // length of the summary text
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
private String id;
@Persistent
@Extension(vendorName="datanucleus", key="gae.pk-name", value="true")
private String url; // this field defines the 'key name' portion of the key
// we do not need to index every field of the StreamItem objects,
// so we mark some as 'unindexed'
@Persistent
private String title;
@Persistent
@Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
private Text description;
@Persistent
@Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
private String descrSummary;
@Persistent
@Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
private String feedDescription;
@Persistent
private String feedUrl;
@Persistent
@Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
private String imageUrl;
@Persistent
@Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
private String feedTitle;
@Persistent
@Extension(vendorName = "datanucleus", key = "gae.unindexed", value="true")
private String author;
@Persistent
private Date date;
@Persistent
Set<Long> ukeys;
public StreamItem() {
}
public StreamItem(String title, String description, String feedDescription, Date date,
String feedTitle, String author, String url, String feedUrl, String imageUrl, Set<Long> ukeys) {
this();
this.title = title;
this.description = new Text(description);
this.feedDescription = feedDescription;
this.date = date;
this.author = author;
this.feedTitle = feedTitle;
this.url = url;
this.feedUrl = feedUrl;
this.imageUrl = imageUrl;
this.descrSummary = null;
this.ukeys = ukeys;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setDescription(String description) {
this.description = new Text(description);
}
public Text getDescription() {
return description;
}
public String getId() {
return id;
}
public Set<Long> getUkeys() {
return ukeys;
}
public void setUkeys(Set<Long> ukeys) {
this.ukeys = ukeys;
}
public String getFeedDescription() {
return feedDescription;
}
public void setFeedDescription(String feedDescription) {
this.feedDescription = feedDescription;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getFeedUrl() {
return feedUrl;
}
public void setFeedUrl(String feedUrl) {
this.feedUrl = feedUrl;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getFeedTitle() {
return feedTitle;
}
public void setFeedTitle(String feedTitle) {
this.feedTitle = feedTitle;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public StreamItemDTO toDTO() {
return new StreamItemDTO(id, url, title, description.getValue(), descrSummary, feedDescription,
feedUrl, imageUrl, feedTitle, author, date);
}
public void removeFromCache() {
CacheSupport.cacheDelete(this.getClass().getName(), id);
}
private String buildDescrSummary() {
String cleaned = Jsoup.clean(description.getValue(), Whitelist.none());
if (cleaned.length() > SUMMARY_LENGTH) {
this.descrSummary = cleaned.substring(0,SUMMARY_LENGTH - 1) + "...";
}
else {
this.descrSummary = cleaned;
}
return this.descrSummary;
}
private String getDescrSummary() {
if (this.descrSummary != null) {
return this.descrSummary;
}
else {
return buildDescrSummary();
}
}
/**
* returns list of feed entries converted to StreamItem objects
*/
public static List<String> buildItems(FeedInfo fi, PersistenceManager pm) {
Set<Long> ukeys = fi.getUkeys();
SyndFeed sf = fi.getFeedInfo().getSyndFeed();
@SuppressWarnings("unchecked")
List<SyndEntry> entries = sf.getEntries();
StreamItem item;
for (SyndEntry entry : entries) {
item = StreamItem.buildItem(fi.getUrl(), sf, entry, ukeys);
pm.makePersistent(item);
}
return null;
}
/**
* convert a feed entry to a StreamItem
*/
@SuppressWarnings("unchecked")
private static StreamItem buildItem(String urlstring, SyndFeed sf, SyndEntry entry,
Set<Long> ukeys) {
StreamItem item = null;
String title = "", url = "", description = "", feedDescription = "", feedTitle = "",
author = "", imageUrl = "";
Date date = null;
String descr = null;
try {
// gather all the information.....
url = entry.getLink();
date = entry.getPublishedDate();
feedTitle = sf.getTitle();
feedDescription = sf.getDescription();
title = Jsoup.clean(entry.getTitle(), Whitelist.simpleText());
// for twitter, first remove the leading author string
if (url.contains("twitter.com")) {
int cindex = title.indexOf(":");
if (cindex > 0) {
title = title.substring(cindex + 1);
}
}
if (entry.getDescription() != null) {
descr = entry.getDescription().getValue();
}
if (descr == null) {
if (entry.getContents().size() > 0) {
SyndContent content = (SyndContent)entry.getContents().get(0);
if (content.getType().equalsIgnoreCase("html")) {
descr = content.getValue();
}
}
}
if (descr != null) {
// sanitize the content
description = Jsoup.clean(descr, Whitelist.basicWithImages());
}
List<SyndPerson> sauthors = entry.getAuthors();
List<String> auths = new ArrayList<String>();
for (SyndPerson auth : sauthors) {
auths.add(auth.getName());
}
author = StringUtils.join(auths, ", ");
SyndImage eImg = sf.getImage();
if (eImg != null) {
imageUrl = eImg.getUrl();
}
else {
// if twitter link...
if (url.contains("twitter.com")) {
// then see if a second link is available-- if so, it should be the link to the image
List<SyndLink> links = entry.getLinks();
if (links.size() >= 2) {
SyndLink imgl = links.get(1);
imageUrl = imgl.getHref();
}
}
}
item = new StreamItem(title, description, feedDescription, date, feedTitle, author,
url, urlstring, imageUrl, ukeys);
item.buildDescrSummary();
}
catch (Exception e) {
e.printStackTrace();
}
return item;
}
public StreamItemSummaryDTO buildSummaryItem() {
String descrSummary = getDescrSummary();
StreamItemSummaryDTO summ = new StreamItemSummaryDTO(id, title, url, descrSummary, date, author, imageUrl);
return summ;
}
public StreamItemSummaryDTO addToCacheGetSumm() {
CacheSupport.cachePutExp(this.getClass().getName(), id, this, CACHE_EXPIR);
return addSummaryToCache();
}
public void addToCache() {
CacheSupport.cachePutExp(this.getClass().getName(), id, this, CACHE_EXPIR);
addSummaryToCache();
}
public StreamItemSummaryDTO addSummaryToCache() {
StreamItemSummaryDTO summ = buildSummaryItem();
CacheSupport.cachePut(StreamItemSummaryDTO.class.getName(), summ.getId(), summ);
return summ;
}
}