001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/podcasts/tags/sakai_2-4-1/podcasts-impl/impl/src/java/org/sakaiproject/component/app/podcasts/BasicPodfeedService.java $
003: * $Id: BasicPodfeedService.java 29142 2007-04-19 00:54:27Z ajpoland@iupui.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.component.app.podcasts;
021:
022: import java.util.ArrayList;
023: import java.util.Date;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.regex.Matcher;
029: import java.util.regex.Pattern;
030: import java.util.ResourceBundle;
031:
032: //import javax.faces.context.FacesContext;
033:
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.sakaiproject.api.app.podcasts.PodcastService;
037: import org.sakaiproject.api.app.podcasts.PodfeedService;
038: import org.sakaiproject.authz.api.SecurityAdvisor;
039: import org.sakaiproject.authz.api.SecurityService;
040: import org.sakaiproject.component.cover.ServerConfigurationService;
041: import org.sakaiproject.content.api.ContentCollection;
042: import org.sakaiproject.content.api.ContentCollectionEdit;
043: import org.sakaiproject.content.api.ContentResource;
044: import org.sakaiproject.entity.api.Entity;
045: import org.sakaiproject.entity.api.ResourceProperties;
046: import org.sakaiproject.entity.api.ResourcePropertiesEdit;
047: import org.sakaiproject.exception.IdUnusedException;
048: import org.sakaiproject.exception.PermissionException;
049: import org.sakaiproject.site.cover.SiteService;
050: import org.sakaiproject.util.ResourceLoader; //import javax.faces.context.FacesContext;
051:
052: import com.sun.syndication.feed.WireFeed;
053: import com.sun.syndication.feed.module.DCModuleImpl;
054: import com.sun.syndication.feed.module.itunes.EntryInformation;
055: import com.sun.syndication.feed.module.itunes.EntryInformationImpl;
056: import com.sun.syndication.feed.module.Module;
057: import com.sun.syndication.feed.rss.Channel;
058: import com.sun.syndication.feed.rss.Description;
059: import com.sun.syndication.feed.rss.Enclosure;
060: import com.sun.syndication.feed.rss.Guid;
061: import com.sun.syndication.feed.rss.Item;
062: import com.sun.syndication.io.FeedException;
063: import com.sun.syndication.io.WireFeedOutput;
064:
065: public class BasicPodfeedService implements PodfeedService {
066:
067: /** MIME type for the global description of the feed **/
068: private static final String DESCRIPTION_CONTENT_TYPE = "text/plain";
069:
070: /** The default feed type. Currently rss_2.0 **/
071: private static final String defaultFeedType = "rss_2.0";
072:
073: /** The default language type. Currently 'en-us' **/
074: private static final String LANGUAGE = "en-us";
075:
076: /** Used to get the global feed title which is a property of Podcasts folder **/
077: private final String PODFEED_TITLE = "podfeedTitle";
078:
079: /** Used to grab the default feed title prefix */
080: private final String FEED_TITLE_STRING = "feed_title";
081:
082: /** Used to get the global feed description which is a property of Podcasts folder **/
083: private final String PODFEED_DESCRIPTION = "podfeedDescription";
084:
085: /** Used to pull copyright statement from sakai.properties file */
086: private final String FEED_COPYRIGHT_STATEMENT = "podfeed_copyrighttext";
087:
088: /** Used to get the copyright statement if stored in Podcasts folder */
089: private final String PODFEED_COPYRIGHT = "feed_copyright";
090:
091: /** Used to pull generator value from sakai.properties file */
092: private final String FEED_GENERATOR_STRING = "podfeed_generator";
093:
094: /** Used to pull generator value from Podcasts folder */
095: private final String PODFEED_GENERATOR = "feed_generator";
096:
097: /** Used to pull item author from sakai.properties file */
098: private final String FEED_ITEM_AUTHOR_STRING = "podfeed_author";
099:
100: /** Used to get the default feed description pieces from the message bundle */
101: private final String FEED_DESC1_STRING = "feed_desc1";
102: private final String FEED_DESC2_STRING = "feed_desc2";
103:
104: /** Used to pull message bundle */
105: private final String PODFEED_MESSAGE_BUNDLE = "org.sakaiproject.api.podcasts.bundle.Messages";
106:
107: private static final Log LOG = LogFactory
108: .getLog(PodcastServiceImpl.class);
109:
110: private PodcastService podcastService;
111: private SecurityService securityService;
112:
113: /**
114: * @param securityService
115: * The securityService to set.
116: */
117: public void setSecurityService(SecurityService securityService) {
118: this .securityService = securityService;
119: }
120:
121: /**
122: * @param podcastService
123: * The podcastService to set.
124: */
125: public void setPodcastService(PodcastService podcastService) {
126: this .podcastService = podcastService;
127: }
128:
129: /**
130: * Gets the podcast folder collection ResourceProperties
131: *
132: * @param siteId
133: * The site id whose podcast folder ResourceProperties wanted
134: *
135: * @return ResourceProperties
136: * The ResourceProperties collection for the podcasts folder
137: */
138: private ResourceProperties getPodcastCollectionProperties(
139: String siteId) {
140: ContentCollection contentCollection = null;
141: ResourceProperties rp = null;
142:
143: try {
144: enablePodfeedSecurityAdvisor();
145:
146: contentCollection = podcastService
147: .getContentCollection(siteId);
148: rp = contentCollection.getProperties();
149:
150: } catch (Exception e) {
151: // catches IdUnusedException, PermissionException
152: LOG
153: .error(
154: e.getMessage()
155: + " attempting to get feed title (getting podcast folder) "
156: + "for site: " + siteId + ". "
157: + e.getMessage(), e);
158: throw new Error(e);
159:
160: } finally {
161: securityService.clearAdvisors();
162: }
163:
164: return rp;
165: }
166:
167: /**
168: * Returns the podfeed global title from content hosting via the podcastService
169: *
170: * @return String
171: * The global podfeed title
172: */
173: public String getPodfeedTitle() {
174: return getPodfeedTitle(podcastService.getSiteId());
175: }
176:
177: /**
178: * Gets the title for the feed from podcast folder's properties.
179: *
180: * @param siteId
181: * The site id
182: *
183: * @return String
184: * The global podfeed title
185: */
186: public String getPodfeedTitle(String siteId) {
187: String feedTitle = null;
188:
189: try {
190: ResourceProperties rp = getPodcastCollectionProperties(siteId);
191:
192: feedTitle = rp.getProperty(PODFEED_TITLE);
193:
194: /* For site where not added to folder upon creation
195: * and has not been revised/updated */
196: if (feedTitle == null) {
197: feedTitle = SiteService.getSite(siteId).getTitle()
198: + getMessageBundleString(FEED_TITLE_STRING);
199: LOG.info("No saved feed title found for site: "
200: + siteId + ". Using " + feedTitle);
201:
202: }
203:
204: } catch (IdUnusedException e) {
205: LOG.error(
206: "IdUnusedException attempting to get feed title (getting podcast folder) "
207: + "for site: " + siteId + ". "
208: + e.getMessage(), e);
209: throw new Error(e);
210:
211: }
212:
213: return feedTitle;
214: }
215:
216: /**
217: * Stores the title for the feed in the podcast folder's resources
218: *
219: * @param String
220: * The title for the feed
221: */
222: public void setPodfeedTitle(String feedTitle) {
223: setPodfeedTitle(feedTitle, podcastService.getSiteId());
224: }
225:
226: /**
227: * Stores the title for the feed in the podcast folder's resources. Used by
228: * the actual feed so need to pass in the siteId also.
229: *
230: * @param feedTitle
231: * The title for the feed
232: * @param siteId
233: * The siteId whose feed is being titled
234: */
235: public void setPodfeedTitle(String feedTitle, String siteId) {
236: storeProperty(PODFEED_TITLE, feedTitle, siteId);
237: }
238:
239: /**
240: * Returns the String of the global feed description
241: */
242: public String getPodfeedDescription() {
243: return getPodfeedDescription(podcastService.getSiteId());
244: }
245:
246: /**
247: * Returns the global feed description.
248: *
249: * @param siteId
250: * The site id to get the feed description from
251: *
252: * @return String
253: * The global feed description
254: */
255: public String getPodfeedDescription(String siteId) {
256: String feedDescription = null;
257:
258: try {
259: ResourceProperties rp = getPodcastCollectionProperties(siteId);
260:
261: feedDescription = rp.getProperty(PODFEED_DESCRIPTION);
262:
263: /* For site where not added to folder upon creation
264: * and has not been revised/updated */
265: if (feedDescription == null) {
266: feedDescription = SiteService.getSite(siteId)
267: .getTitle()
268: + getMessageBundleString(FEED_DESC1_STRING)
269: + getMessageBundleString(FEED_DESC2_STRING);
270: LOG.info("No feed description found for site: "
271: + siteId + ". Using " + feedDescription);
272: }
273: } catch (IdUnusedException e) {
274: LOG.error(
275: "IdUnusedException attempting to get feed title (getting podcast folder) "
276: + "for site: " + siteId + ". "
277: + e.getMessage(), e);
278: throw new Error(e);
279: }
280:
281: return feedDescription;
282: }
283:
284: /**
285: * Returns the global feed generator String.
286: */
287: public void setPodfeedDescription(String feedDescription) {
288: setPodfeedDescription(feedDescription, podcastService
289: .getSiteId());
290: }
291:
292: /**
293: * Sets the description for the feed.
294: *
295: * @param feedDescription
296: * The String containing the feed description.
297: * @param siteId
298: * The siteId where to store the description
299: */
300: public void setPodfeedDescription(String feedDescription,
301: String siteId) {
302: storeProperty(PODFEED_DESCRIPTION, feedDescription, siteId);
303: }
304:
305: public String getPodfeedGenerator() {
306: return getPodfeedGenerator(podcastService.getSiteId());
307: }
308:
309: /**
310: * Returns the global feed generator string.
311: *
312: * @param siteId
313: * The site id to get the feed description from
314: *
315: * @return String
316: * The global feed generator string.
317: */
318: public String getPodfeedGenerator(String siteId) {
319: return retrievePropValue(PODFEED_GENERATOR, siteId,
320: FEED_GENERATOR_STRING);
321: }
322:
323: /**
324: *
325: */
326: public void setPodfeedGenerator(String feedGenerator) {
327: setPodfeedGenerator(feedGenerator, podcastService.getSiteId());
328: }
329:
330: /**
331: * Sets the description for the feed.
332: *
333: * @param feedDescription
334: * The String containing the feed description.
335: * @param siteId
336: * The siteId where to store the description
337: */
338: public void setPodfeedGenerator(String feedGenerator, String siteId) {
339: storeProperty(PODFEED_GENERATOR, feedGenerator, siteId);
340: }
341:
342: public String getPodfeedCopyright() {
343: return getPodfeedCopyright(podcastService.getSiteId());
344: }
345:
346: /**
347: * Returns the global feed generator string.
348: *
349: * @param siteId
350: * The site id to get the feed description from
351: *
352: * @return String
353: * The global feed generator string.
354: */
355: public String getPodfeedCopyright(String siteId) {
356: return retrievePropValue(PODFEED_COPYRIGHT, siteId,
357: FEED_COPYRIGHT_STATEMENT);
358: }
359:
360: /**
361: * Sets feed copyright statement from within site.
362: */
363: public void setPodfeedCopyright(String feedCopyright) {
364: setPodfeedCopyright(feedCopyright, podcastService.getSiteId());
365: }
366:
367: /**
368: * Sets the description for the feed.
369: *
370: * @param feedDescription
371: * The String containing the feed description.
372: * @param siteId
373: * The siteId where to store the description
374: */
375: public void setPodfeedCopyright(String feedCopyright, String siteId) {
376: storeProperty(PODFEED_COPYRIGHT, feedCopyright, siteId);
377: }
378:
379: /**
380: * Returns the property value for the property requested if stored within the
381: * Podcasts folder resource of the site id passed in. If not stored, retrieves
382: * the value from the Message bundle.
383: *
384: * @param propName
385: * The name of the property wanted.
386: *
387: * @param siteId
388: * The id of the site wanted.
389: *
390: * @param bundleName
391: * The name within the Message bundle for the default string.
392: *
393: * @return
394: * String containing either the stored property or the default Message bundle one.
395: */
396: private String retrievePropValue(String propName, String siteId,
397: String bundleName) {
398: String propValue = null;
399:
400: ResourceProperties rp = getPodcastCollectionProperties(siteId);
401:
402: propValue = rp.getProperty(propName);
403:
404: /* For site where not added to folder upon creation
405: * and has not been revised/updated */
406: if (propValue == null) {
407: propValue = getMessageBundleString(bundleName);
408: LOG.info("No property " + propName + " stored for site: "
409: + siteId + ". Using " + propValue);
410: }
411:
412: return propValue;
413:
414: }
415:
416: /**
417: * Stores the property propValue in the Podcasts folder resource under the name propName
418: *
419: * @param propName
420: * The name within the resource to store the value
421: *
422: * @param propValue
423: * The value to store
424: *
425: * @param siteId
426: * Which site's Podcasts folder to store this property within.
427: */
428: private void storeProperty(final String propName, String propValue,
429: String siteId) {
430: ContentCollectionEdit contentCollection = null;
431:
432: try {
433: contentCollection = podcastService
434: .getContentCollectionEditable(siteId);
435: ResourcePropertiesEdit rp = contentCollection
436: .getPropertiesEdit();
437:
438: if (rp.getProperty(propName) != null) {
439: rp.removeProperty(propName);
440: }
441:
442: rp.addProperty(propName, propValue);
443:
444: podcastService.commitContentCollection(contentCollection);
445: } catch (Exception e) {
446: // catches IdUnusedException, PermissionException
447: LOG.error(e.getMessage() + " attempting to add property "
448: + propName + " for site: " + siteId + ". "
449: + e.getMessage(), e);
450: podcastService.cancelContentCollection(contentCollection);
451:
452: throw new Error(e);
453: }
454:
455: }
456:
457: /**
458: * This method generates the RSS feed
459: *
460: * @return String
461: * The feed XML file as a string
462: */
463: public String generatePodcastRSS() {
464: return generatePodcastRSS(podcastService.getSiteId(), null);
465:
466: }
467:
468: /**
469: * This method generates the RSS feed
470: *
471: * @param siteID
472: * The site id whose feed needs to be generated
473: * @param ftyle
474: * The feed type (for potential future development) - currently rss 2.0
475: *
476: * @return String
477: * The feed document as a String
478: */
479: public String generatePodcastRSS(String siteId, String ftype) {
480: final String feedType = (ftype != null) ? ftype
481: : defaultFeedType;
482: Date pubDate = null;
483: Date lastBuildDate = null;
484:
485: // put each podcast entry/episode into a list
486: List entries = populatePodcastArray(siteId);
487:
488: // Pull first entry if not null in order to establish publish date
489: // for entire feed. Pull the second to get lastBuildDate
490: if (entries != null) {
491: Iterator iter = entries.iterator();
492:
493: if (iter.hasNext()) {
494: Item firstPodcast = (Item) iter.next();
495:
496: pubDate = firstPodcast.getPubDate();
497:
498: if (iter.hasNext()) {
499: Item nextPodcast = (Item) iter.next();
500:
501: lastBuildDate = nextPodcast.getPubDate();
502:
503: } else {
504: // only one, so use the first podcast date
505: lastBuildDate = pubDate;
506: }
507:
508: } else {
509: // There are no podcasts to present, so use today
510: pubDate = new Date();
511: lastBuildDate = pubDate;
512: }
513: }
514:
515: // pull global information for the feed into a Map so
516: // can be passed all at once
517: Map feedInfo = new HashMap();
518:
519: feedInfo.put("title", getPodfeedTitle(siteId));
520: feedInfo.put("desc", getPodfeedDescription(siteId));
521: feedInfo.put("gen", getPodfeedGenerator(siteId));
522:
523: // This is the URL for the actual feed.
524: feedInfo.put("url", ServerConfigurationService.getServerUrl()
525: + Entity.SEPARATOR + "podcasts/site/" + siteId);
526:
527: feedInfo.put("copyright", getPodfeedCopyright(siteId));
528:
529: final WireFeed podcastFeed = doSyndication(feedInfo, entries,
530: feedType, pubDate, lastBuildDate);
531:
532: final WireFeedOutput wireWriter = new WireFeedOutput();
533:
534: try {
535: return wireWriter.outputString(podcastFeed);
536:
537: } catch (FeedException e) {
538: LOG.error(
539: "Feed exception while attempting to write out the final xml file. "
540: + "for site: " + siteId + ". "
541: + e.getMessage(), e);
542: throw new Error(e);
543:
544: }
545: }
546:
547: /**
548: * This pulls the podcasts from Resourses and stuffs it in a list to be
549: * added to the feed
550: *
551: * @param siteId
552: * The site to pull the individual podcasts from
553: *
554: * @return List The list of podcast entries from ContentHosting
555: */
556: private List populatePodcastArray(String siteId) {
557: List podEntries = null;
558: List entries = new ArrayList();
559:
560: try {
561: enablePodfeedSecurityAdvisor();
562:
563: // get the individual podcasts
564: podEntries = podcastService.getPodcasts(siteId);
565:
566: // remove any that are in the future
567: podEntries = podcastService.filterPodcasts(podEntries);
568:
569: } catch (PermissionException e) {
570: LOG.error(
571: "PermissionException getting podcasts in order to generate podfeed for site: "
572: + siteId + ". " + e.getMessage(), e);
573: throw new Error(e);
574:
575: } catch (Exception e) {
576: LOG.info(e.getMessage() + "for site: " + siteId, e);
577: throw new Error(e);
578: } finally {
579: securityService.clearAdvisors();
580: }
581:
582: if (podEntries != null) {
583: // get the iterator
584: Iterator podcastIter = podEntries.iterator();
585:
586: while (podcastIter.hasNext()) {
587:
588: // get its properties from ContentHosting
589: ContentResource podcastResource = (ContentResource) podcastIter
590: .next();
591: ResourceProperties podcastProperties = podcastResource
592: .getProperties();
593:
594: // publish date for this particular podcast
595: Date publishDate = null;
596:
597: try {
598: // need to put in GMT for the feed
599: publishDate = podcastService
600: .getGMTdate(podcastProperties
601: .getTimeProperty(
602: PodcastService.DISPLAY_DATE)
603: .getTime());
604:
605: } catch (Exception e) {
606: // catches EntityPropertyNotDefinedException, EntityPropertyTypeException
607: LOG
608: .warn(e.getMessage()
609: + " generating podfeed getting DISPLAY_DATE for entry for site: "
610: + siteId
611: + "while building feed. SKIPPING... "
612: + e.getMessage());
613:
614: }
615:
616: // if getting the date generates an error, skip this podcast.
617: if (publishDate != null) {
618: try {
619: Map podcastMap = new HashMap();
620: podcastMap.put("date", publishDate);
621: podcastMap
622: .put(
623: "title",
624: podcastProperties
625: .getPropertyFormatted(ResourceProperties.PROP_DISPLAY_NAME));
626:
627: enablePodfeedSecurityAdvisor();
628: String fileUrl = podcastService
629: .getPodcastFileURL(podcastResource
630: .getId());
631: podcastMap.put("guid", fileUrl);
632: final String podcastFolderId = podcastService
633: .retrievePodcastFolderId(siteId);
634: securityService.clearAdvisors();
635:
636: // if site Display to Site, need to access actual podcasts thru Dav servlet
637: // so change item URLs to do so
638: if (!podcastService.isPublic(podcastFolderId)) {
639: fileUrl = convertToDavUrl(fileUrl);
640: }
641:
642: podcastMap.put("url", fileUrl);
643: podcastMap
644: .put(
645: "description",
646: podcastProperties
647: .getPropertyFormatted(ResourceProperties.PROP_DESCRIPTION));
648: podcastMap
649: .put(
650: "author",
651: podcastProperties
652: .getPropertyFormatted(ResourceProperties.PROP_CREATOR));
653: podcastMap
654: .put(
655: "len",
656: Long
657: .parseLong(podcastProperties
658: .getProperty(ResourceProperties.PROP_CONTENT_LENGTH)));
659: podcastMap
660: .put(
661: "type",
662: podcastProperties
663: .getProperty(ResourceProperties.PROP_CONTENT_TYPE));
664:
665: entries.add(addPodcast(podcastMap));
666:
667: } catch (PermissionException e) {
668: // Problem with this podcast file - LOG and skip
669: LOG
670: .error("PermissionException generating podfeed while adding entry for site: "
671: + siteId
672: + ". SKIPPING... "
673: + e.getMessage());
674:
675: } catch (IdUnusedException e) {
676: // Problem with this podcast file - LOG and skip
677: LOG
678: .warn("IdUnusedException generating podfeed while adding entry for site: "
679: + siteId
680: + ". SKIPPING... "
681: + e.getMessage());
682:
683: }
684: }
685:
686: }
687:
688: }
689:
690: securityService.clearAdvisors();
691:
692: return entries;
693:
694: }
695:
696: /**
697: * This add a particular podcast to the feed.
698: *
699: * @param title
700: * The title for this podcast
701: * @param mp3link
702: * The URL where the podcast is stored
703: * @param date
704: * The publish date for this podcast
705: * @param blogContent
706: * The description of this podcast
707: * @param cat
708: * The category of entry this is (Podcast)
709: * @param author
710: * The author of this podcast
711: *
712: * @return
713: * A SyndEntryImpl for this podcast
714: */
715: private Item addPodcast(Map values) {
716: final Item item = new Item();
717:
718: // set title for this podcast
719: item.setTitle((String) values.get("title"));
720:
721: // Replace all occurrences of pattern (ie, spaces) in input
722: // with hex equivalent (%20)
723: Pattern pattern = Pattern.compile(" ");
724: String url = (String) values.get("url");
725: Matcher matcher = pattern.matcher(url);
726: url = matcher.replaceAll("%20");
727: item.setLink(url);
728:
729: // Set Publish date for this podcast/episode
730: // NOTE: date has local time, but when feed rendered,
731: // converts it to GMT
732: item.setPubDate((Date) values.get("date"));
733:
734: // Set description for this podcast/episode
735: final Description itemDescription = new Description();
736: itemDescription.setType(DESCRIPTION_CONTENT_TYPE);
737: itemDescription.setValue((String) values.get("description"));
738: item.setDescription(itemDescription);
739:
740: // Set guid for this podcast/episode
741: item.setGuid(new Guid());
742: item.getGuid().setValue((String) values.get("guid"));
743: item.getGuid().setPermaLink(false);
744:
745: // This creates the enclosure so podcatchers (iTunes) can
746: // find the podcasts
747: List enclosures = new ArrayList();
748:
749: final Enclosure enc = new Enclosure();
750: enc.setUrl(url);
751: enc.setType((String) values.get("type"));
752: enc.setLength((Long) values.get("len"));
753:
754: enclosures.add(enc);
755:
756: item.setEnclosures(enclosures);
757:
758: // Currently uses 2 modules:
759: // iTunes for podcasting
760: // DCmodule since validators want email with author tag,
761: // so use dc:creator instead
762: List modules = new ArrayList();
763:
764: // Generate the iTunes tags
765: final EntryInformation iTunesModule = new EntryInformationImpl();
766:
767: iTunesModule
768: .setAuthor(getMessageBundleString(FEED_ITEM_AUTHOR_STRING));
769: iTunesModule.setSummary((String) values.get("description"));
770:
771: // Set dc:creator tag
772: final DCModuleImpl dcModule = new DCModuleImpl();
773:
774: dcModule.setCreator((String) values.get("author"));
775:
776: modules.add(iTunesModule);
777: modules.add(dcModule);
778:
779: item.setModules(modules);
780:
781: return item;
782: }
783:
784: /**
785: * This puts the pieces together to generate the actual feed.
786: *
787: * @param title
788: * The global title for the podcast
789: * @param link
790: * The URL for the feed
791: * @param description_loc
792: * Global description of the feed
793: * @param copyright
794: * Copyright information
795: * @param entries
796: * The list of individual podcasts
797: * @param feedTyle
798: * The output feed type (for potential future development). Set to rss_2.0
799: * @param pubDate
800: * The date to set the publish date for this feed
801: *
802: * @eturn SyndFeed
803: * The entire podcast stuffed into a SyndFeed object
804: */
805: private Channel doSyndication(Map feedInfo, List entries,
806: String feedType, Date pubDate, Date lastBuildDate) {
807:
808: final Channel channel = new Channel();
809:
810: // FUTURE: How to determine what podcatcher supports and feed that to them
811: channel.setFeedType(feedType);
812: channel.setTitle((String) feedInfo.get("title"));
813: channel.setLanguage(LANGUAGE);
814:
815: channel.setPubDate(pubDate);
816: channel.setLastBuildDate(lastBuildDate);
817:
818: channel.setLink((String) feedInfo.get("url"));
819: channel.setDescription((String) feedInfo.get("desc"));
820: channel.setCopyright((String) feedInfo.get("copyright"));
821: channel.setGenerator((String) feedInfo.get("gen"));
822:
823: channel.setItems(entries);
824:
825: // Used to remove the dc: tags from the channel level info
826: List modules = new ArrayList();
827:
828: channel.setModules(modules);
829:
830: return channel;
831: }
832:
833: /**
834: * Establish a security advisor to allow the "embedded" azg work to occur
835: * with no need for additional security permissions.
836: */
837: protected void enablePodfeedSecurityAdvisor() {
838: // put in a security advisor so we can do our podcast work without need
839: // of further permissions
840: securityService.pushAdvisor(new SecurityAdvisor() {
841: public SecurityAdvice isAllowed(String userId,
842: String function, String reference) {
843: return SecurityAdvice.ALLOWED;
844: }
845: });
846: }
847:
848: /**
849: * Returns podcast folder id using either 'podcasts' or 'Podcasts'. If it
850: * cannot find or is denied access, it will return null.
851: *
852: * @param siteId
853: * The site to search
854: *
855: * @return String
856: * Contains the complete id for the podcast folder
857: */
858: public String retrievePodcastFolderId(String siteId) {
859:
860: String podcastFolderId = null;
861:
862: try {
863: enablePodfeedSecurityAdvisor();
864: podcastFolderId = podcastService
865: .retrievePodcastFolderId(siteId);
866:
867: } catch (PermissionException e) {
868: // log and return null to indicate there was a problem generating
869: LOG
870: .error("PermissionException while trying to retrieve Podcast folder Id string "
871: + "for site " + siteId + e.getMessage());
872:
873: } finally {
874: securityService.clearAdvisors();
875:
876: }
877:
878: return podcastFolderId;
879:
880: }
881:
882: /**
883: * If site is Display to Site, need to retrieve files thru dav servlet.
884: * This converts a podcast URL to accomplish this.
885: *
886: * @param fileUrl
887: * The current file URL. Access is through content.
888: *
889: * @return String
890: * The changed URL that points to the dav servlet.
891: */
892: private String convertToDavUrl(String fileUrl) {
893: // Compile regular expression
894: Pattern pattern = Pattern.compile("access/content/group");
895:
896: // Replace all occurrences of pattern in input
897: Matcher matcher = pattern.matcher(fileUrl);
898: fileUrl = matcher.replaceAll("dav");
899:
900: return fileUrl;
901:
902: }
903:
904: /**
905: * Determines if authenticated user has 'read' access to podcast collection folder
906: *
907: * @param id
908: * The id for the podcast collection folder
909: *
910: * @return
911: * TRUE - has read access, FALSE - does not
912: */
913: public boolean allowAccess(String id) {
914: return podcastService.allowAccess(id);
915: }
916:
917: /**
918: * Sets the Faces error message by pulling the message from the
919: * MessageBundle using the name passed in
920: *
921: * @param key
922: * The name in the MessageBundle for the message wanted
923: *
924: * @return String
925: * The string that is the value of the message
926: */
927: private String getMessageBundleString(String key) {
928:
929: ResourceBundle resbud = ResourceBundle
930: .getBundle(PODFEED_MESSAGE_BUNDLE);
931:
932: return resbud.getString(key);
933: }
934: }
|