diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml
index cb64f95a49..46add5b9d2 100644
--- a/dspace-api/pom.xml
+++ b/dspace-api/pom.xml
@@ -272,6 +272,11 @@
contiperf
test
+
+ org.rometools
+ rome-modules
+ 1.0
+
diff --git a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java
index 2142bc3d48..497ddc6ee1 100644
--- a/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java
+++ b/dspace-api/src/main/java/org/dspace/app/util/SyndicationFeed.java
@@ -9,13 +9,16 @@ package org.dspace.app.util;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Document;
import org.dspace.content.Bitstream;
@@ -25,6 +28,7 @@ import org.dspace.content.DCDate;
import org.dspace.content.DCValue;
import org.dspace.content.DSpaceObject;
import org.dspace.content.Item;
+import org.dspace.content.MetadataSchema;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
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.SyndEntry;
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.SyndImageImpl;
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.DCModule;
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.FeedException;
import org.apache.log4j.Logger;
+import org.dspace.content.Bundle;
/**
* 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 defaultDateField = "dc.date.issued";
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:
private static String titleField =
@@ -96,6 +106,9 @@ public class SyndicationFeed
private static String authorField =
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)
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
private String uiType = null;
+ private HttpServletRequest request = null;
+
/**
* Constructor.
* @param ui either "xmlui" or "jspui"
@@ -145,6 +160,8 @@ public class SyndicationFeed
String logoURL = null;
String objectURL = null;
String defaultTitle = null;
+ boolean podcastFeed = false;
+ this.request = request;
// dso is null for the whole site, or a search without scope
if (dso == null)
@@ -163,6 +180,10 @@ public class SyndicationFeed
defaultTitle = col.getMetadata("name");
feed.setDescription(col.getMetadata("short_description"));
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)
{
@@ -170,6 +191,10 @@ public class SyndicationFeed
defaultTitle = comm.getMetadata("name");
feed.setDescription(comm.getMetadata("short_description"));
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);
if (logo != null)
@@ -190,7 +215,11 @@ public class SyndicationFeed
// be contained in the rdf. Not all RSS-viewers show this logo.
SyndImage image = new SyndImageImpl();
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);
feed.setImage(image);
}
@@ -328,6 +357,79 @@ public class SyndicationFeed
}
entry.getModules().add(dc);
}
+
+ //iTunes Podcast Support - START
+ if (podcastFeed)
+ {
+ // Add enclosure(s)
+ List 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.setSubtitle(title == null ? localize(labels, MSG_UNTITLED) : title); //
+
+ if (db.length() > 0) {
+ itunes.setSummary(db.toString()); //
+ }
+
+ 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)); //
+ }
+
+ String subject = getOneDC(item, "dc.subject");
+ if (subject != null && subject.length() > 0) {
+ String[] subjects = new String[1];
+ subjects[0] = subject;
+ itunes.setKeywords(subjects); //
+ }
+
+ entry.getModules().add(itunes);
+ }
}
feed.setEntries(entries);
}
diff --git a/dspace/config/dspace.cfg b/dspace/config/dspace.cfg
index cf43eecb21..5993c9c8ed 100644
--- a/dspace/config/dspace.cfg
+++ b/dspace/config/dspace.cfg
@@ -1388,6 +1388,20 @@ webui.feed.item.author = dc.contributor.author
# Must be an absolute URL, e.g.
## 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 ####
# NB: for result data formatting, OpenSearch uses Syndication Feed Settings
# so even if Syndication Feeds are not enabled, they must be configured