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