DS-528 RSS feed to support iTunes Podcast

git-svn-id: http://scm.dspace.org/svn/repo/dspace/trunk@6531 9c30dcfa-912a-0410-8fc2-9e0234be79fd
This commit is contained in:
Peter Dietz
2011-08-04 21:40:03 +00:00
parent 09f561804e
commit d2964aa470
3 changed files with 122 additions and 1 deletions

View File

@@ -272,6 +272,11 @@
<artifactId>contiperf</artifactId> <artifactId>contiperf</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.rometools</groupId>
<artifactId>rome-modules</artifactId>
<version>1.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -9,13 +9,16 @@ package org.dspace.app.util;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.dspace.content.Bitstream; import org.dspace.content.Bitstream;
@@ -25,6 +28,7 @@ import org.dspace.content.DCDate;
import org.dspace.content.DCValue; import org.dspace.content.DCValue;
import org.dspace.content.DSpaceObject; import org.dspace.content.DSpaceObject;
import org.dspace.content.Item; import org.dspace.content.Item;
import org.dspace.content.MetadataSchema;
import org.dspace.core.ConfigurationManager; import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants; import org.dspace.core.Constants;
import org.dspace.handle.HandleManager; import org.dspace.handle.HandleManager;
@@ -33,6 +37,8 @@ import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl; import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl; import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndEnclosure;
import com.sun.syndication.feed.synd.SyndEnclosureImpl;
import com.sun.syndication.feed.synd.SyndImage; import com.sun.syndication.feed.synd.SyndImage;
import com.sun.syndication.feed.synd.SyndImageImpl; import com.sun.syndication.feed.synd.SyndImageImpl;
import com.sun.syndication.feed.synd.SyndPerson; import com.sun.syndication.feed.synd.SyndPerson;
@@ -42,10 +48,13 @@ import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.module.DCModuleImpl; import com.sun.syndication.feed.module.DCModuleImpl;
import com.sun.syndication.feed.module.DCModule; import com.sun.syndication.feed.module.DCModule;
import com.sun.syndication.feed.module.Module; import com.sun.syndication.feed.module.Module;
import com.sun.syndication.feed.module.itunes.*;
import com.sun.syndication.feed.module.itunes.types.Duration;
import com.sun.syndication.io.SyndFeedOutput; import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.io.FeedException; import com.sun.syndication.io.FeedException;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.dspace.content.Bundle;
/** /**
* Invoke ROME library to assemble a generic model of a syndication * Invoke ROME library to assemble a generic model of a syndication
@@ -80,6 +89,7 @@ public class SyndicationFeed
private static String defaultAuthorField = "dc.contributor.author"; private static String defaultAuthorField = "dc.contributor.author";
private static String defaultDateField = "dc.date.issued"; private static String defaultDateField = "dc.date.issued";
private static String defaultDescriptionFields = "dc.description.abstract, dc.description, dc.title.alternative, dc.title"; private static String defaultDescriptionFields = "dc.description.abstract, dc.description, dc.title.alternative, dc.title";
private static String defaultExternalMedia = "dc.source.uri";
// metadata field for Item title in entry: // metadata field for Item title in entry:
private static String titleField = private static String titleField =
@@ -96,6 +106,9 @@ public class SyndicationFeed
private static String authorField = private static String authorField =
getDefaultedConfiguration("webui.feed.item.author", defaultAuthorField); getDefaultedConfiguration("webui.feed.item.author", defaultAuthorField);
// metadata field for Item external media source url
private static String sourceField = getDefaultedConfiguration("webui.feed.item.sourceuri", defaultExternalMedia);
// metadata field for Item dc:creator field in entry's DCModule (no default) // metadata field for Item dc:creator field in entry's DCModule (no default)
private static String dcCreatorField = ConfigurationManager.getProperty("webui.feed.item.dc.creator"); private static String dcCreatorField = ConfigurationManager.getProperty("webui.feed.item.dc.creator");
@@ -115,6 +128,8 @@ public class SyndicationFeed
// affects Bitstream retrieval URL and I18N keys // affects Bitstream retrieval URL and I18N keys
private String uiType = null; private String uiType = null;
private HttpServletRequest request = null;
/** /**
* Constructor. * Constructor.
* @param ui either "xmlui" or "jspui" * @param ui either "xmlui" or "jspui"
@@ -145,6 +160,8 @@ public class SyndicationFeed
String logoURL = null; String logoURL = null;
String objectURL = null; String objectURL = null;
String defaultTitle = null; String defaultTitle = null;
boolean podcastFeed = false;
this.request = request;
// dso is null for the whole site, or a search without scope // dso is null for the whole site, or a search without scope
if (dso == null) if (dso == null)
@@ -163,6 +180,10 @@ public class SyndicationFeed
defaultTitle = col.getMetadata("name"); defaultTitle = col.getMetadata("name");
feed.setDescription(col.getMetadata("short_description")); feed.setDescription(col.getMetadata("short_description"));
logo = col.getLogo(); logo = col.getLogo();
String cols = ConfigurationManager.getProperty("webui.feed.podcast.collections");
if(cols != null && cols.length() > 1 && cols.contains(col.getHandle()) ) {
podcastFeed = true;
}
} }
else if (dso.getType() == Constants.COMMUNITY) else if (dso.getType() == Constants.COMMUNITY)
{ {
@@ -170,6 +191,10 @@ public class SyndicationFeed
defaultTitle = comm.getMetadata("name"); defaultTitle = comm.getMetadata("name");
feed.setDescription(comm.getMetadata("short_description")); feed.setDescription(comm.getMetadata("short_description"));
logo = comm.getLogo(); logo = comm.getLogo();
String comms = ConfigurationManager.getProperty("webui.feed.podcast.communities");
if(comms != null && comms.length() > 1 && comms.contains(comm.getHandle()) ){
podcastFeed = true;
}
} }
objectURL = resolveURL(request, dso); objectURL = resolveURL(request, dso);
if (logo != null) if (logo != null)
@@ -190,7 +215,11 @@ public class SyndicationFeed
// be contained in the rdf. Not all RSS-viewers show this logo. // be contained in the rdf. Not all RSS-viewers show this logo.
SyndImage image = new SyndImageImpl(); SyndImage image = new SyndImageImpl();
image.setLink(objectURL); image.setLink(objectURL);
image.setTitle(localize(labels, MSG_LOGO_TITLE)); if (StringUtils.isNotBlank(feed.getTitle())) {
image.setTitle(feed.getTitle());
} else {
image.setTitle(localize(labels, MSG_LOGO_TITLE));
}
image.setUrl(logoURL); image.setUrl(logoURL);
feed.setImage(image); feed.setImage(image);
} }
@@ -328,6 +357,79 @@ public class SyndicationFeed
} }
entry.getModules().add(dc); entry.getModules().add(dc);
} }
//iTunes Podcast Support - START
if (podcastFeed)
{
// Add enclosure(s)
List<SyndEnclosure> enclosures = new ArrayList();
try {
Bundle[] bunds = item.getBundles("ORIGINAL");
if (bunds[0] != null) {
Bitstream[] bits = bunds[0].getBitstreams();
for (int i = 0; (i < bits.length); i++) {
String mime = bits[i].getFormat().getMIMEType();
if(mime.contains("audio/x-mpeg")) {
SyndEnclosure enc = new SyndEnclosureImpl();
enc.setType(bits[i].getFormat().getMIMEType());
enc.setLength(bits[i].getSize());
enc.setUrl(urlOfBitstream(request, bits[i]));
enclosures.add(enc);
} else {
continue;
}
}
}
//Also try to add an external value from dc.identifier.other
// We are assuming that if this is set, then it is a media file
DCValue[] externalMedia = item.getMetadata(sourceField);
if(externalMedia.length > 0)
{
for(int i = 0; i< externalMedia.length; i++)
{
SyndEnclosure enc = new SyndEnclosureImpl();
enc.setType("audio/x-mpeg");
enc.setLength(1);
enc.setUrl(externalMedia[i].value);
enclosures.add(enc);
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
entry.setEnclosures(enclosures);
// Get iTunes specific fields: author, subtitle, summary, duration, keywords
EntryInformation itunes = new EntryInformationImpl();
String author = getOneDC(item, authorField);
if (author != null && author.length() > 0) {
itunes.setAuthor(author); // <itunes:author>
}
itunes.setSubtitle(title == null ? localize(labels, MSG_UNTITLED) : title); // <itunes:subtitle>
if (db.length() > 0) {
itunes.setSummary(db.toString()); // <itunes:summary>
}
String extent = getOneDC(item, "dc.format.extent"); // assumed that user will enter this field with length of song in seconds
if (extent != null && extent.length() > 0) {
extent = extent.split(" ")[0];
Integer duration = Integer.parseInt(extent);
itunes.setDuration(new Duration(duration)); // <itunes:duration>
}
String subject = getOneDC(item, "dc.subject");
if (subject != null && subject.length() > 0) {
String[] subjects = new String[1];
subjects[0] = subject;
itunes.setKeywords(subjects); // <itunes:keywords>
}
entry.getModules().add(itunes);
}
} }
feed.setEntries(entries); feed.setEntries(entries);
} }

View File

@@ -1388,6 +1388,20 @@ webui.feed.item.author = dc.contributor.author
# Must be an absolute URL, e.g. # Must be an absolute URL, e.g.
## webui.feed.logo.url = ${dspace.url}/themes/mysite/images/mysite-logo.png ## webui.feed.logo.url = ${dspace.url}/themes/mysite/images/mysite-logo.png
# iTunes Podcast Enhanced RSS Feed Properties
# Add all the communities / collections, separated by commas (no spaces) that should
# have the iTunes podcast metadata added to their RSS feed.
# Default: Disabled, No collections or communities have iTunes Podcast enhanced metadata in their feed.
# webui.feed.podcast.collections =123456789/2,123456789/3
# webui.feed.podcast.communities =123456789/1
# For the iTunes Podcast Feed, if you would like to specify an external media file,
# not on your DSpace server to be enclosed within the entry for each item,
# specify which metadata field will hold the URI to the external media file.
# This is useful if you store the metadata in DSpace, and a seperate streaming server to host the media.
# Default: dc.source.uri
#webui.feed.item.sourceuri = dc.source.uri
#### OpenSearch Settings #### #### OpenSearch Settings ####
# NB: for result data formatting, OpenSearch uses Syndication Feed Settings # NB: for result data formatting, OpenSearch uses Syndication Feed Settings
# so even if Syndication Feeds are not enabled, they must be configured # so even if Syndication Feeds are not enabled, they must be configured