/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/podcasts/trunk/podcasts-impl/impl/src/java/org/sakaiproject/component/app/podcasts/PodcastServiceImpl.java $ * $Id: PodcastServiceImpl.java 120353 2013-02-21 15:58:11Z matthew.buckett@it.ox.ac.uk $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 The Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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. * **********************************************************************************/ package org.sakaiproject.component.app.podcasts; import static org.sakaiproject.component.app.podcasts.Utilities.checkSet; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.api.app.podcasts.PodcastPermissionsService; import org.sakaiproject.api.app.podcasts.PodcastService; import org.sakaiproject.api.app.podcasts.exception.PodcastException; import org.sakaiproject.authz.api.SecurityAdvisor; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.content.api.ContentCollection; import org.sakaiproject.content.api.ContentCollectionEdit; import org.sakaiproject.content.api.ContentHostingService; import org.sakaiproject.content.api.ContentResource; import org.sakaiproject.content.api.ContentResourceEdit; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.entity.api.EntityPropertyNotDefinedException; import org.sakaiproject.entity.api.EntityPropertyTypeException; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.entity.api.ResourcePropertiesEdit; import org.sakaiproject.event.api.Event; import org.sakaiproject.event.api.NotificationService; import org.sakaiproject.event.api.EventTrackingService; import org.sakaiproject.exception.IdInvalidException; import org.sakaiproject.exception.IdLengthException; import org.sakaiproject.exception.IdUniquenessException; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.IdUsedException; import org.sakaiproject.exception.InUseException; import org.sakaiproject.exception.InconsistentException; import org.sakaiproject.exception.OverQuotaException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.exception.ServerOverloadException; import org.sakaiproject.exception.TypeException; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.time.api.Time; import org.sakaiproject.time.api.TimeService; import org.sakaiproject.tool.api.ToolManager; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.util.ResourceLoader; public class PodcastServiceImpl implements PodcastService { /** Used to retrieve global podcast title and description from podcast folder collection **/ private static final String PODFEED_TITLE = "podfeedTitle"; private static final String PODFEED_DESCRIPTION = "podfeedDescription"; /** Used to grab the default feed title prefix */ private static final String FEED_TITLE_STRING = "feed_title"; /** Used to get the default feed description pieces from the message bundle */ private static final String FEED_DESC1_STRING = "feed_desc1"; private static final String FEED_DESC2_STRING = "feed_desc2"; /** Used to pull message bundle */ private static final String PODFEED_MESSAGE_BUNDLE = "org.sakaiproject.api.podcasts.bundle.Messages"; // private ResourceBundle resbud = ResourceBundle.getBundle(PODFEED_MESSAGE_BUNDLE); private static ResourceLoader resbud = new ResourceLoader(PODFEED_MESSAGE_BUNDLE); /** Used for event tracking of podcasts - adding a podcast **/ private static final String EVENT_ADD_PODCAST = "podcast.add"; /** Used for event tracking of podcasts - revisiong a podcast **/ private static final String EVENT_REVISE_PODCAST = "podcast.revise"; /** Used for event tracking of podcasts - deleting a podcast **/ private static final String EVENT_DELETE_PODCAST = "podcast.delete"; /** Options. 0 = Display to non-members, 1 = Display to Site * */ private static final int PUBLIC = 0; private static final int SITE = 1; private static Log LOG = LogFactory.getLog(PodcastServiceImpl.class); private Reference siteRef; // injected beans private ContentHostingService contentHostingService; private ToolManager toolManager; private SessionManager sessionManager; private PodcastPermissionsService podcastPermissionsService; private UserDirectoryService userDirectoryService; private TimeService timeService; private SecurityService securityService; private SiteService siteService; private EventTrackingService eventTrackingService; // FUTURE; TO BE IMPLEMENTED // private NotificationService notificationService; /** Needed for when Notification implemented * */ protected String m_relativeAccessPoint = null; PodcastServiceImpl() { } /** Injects ContentHostingService into this service **/ public void setContentHostingService(ContentHostingService chs) { this.contentHostingService = chs; } /** Injects ToolManager into this service **/ public void setToolManager(ToolManager tm) { toolManager = tm; } /** Injects the UserDirectoryService into this service **/ public void setUserDirectoryService(UserDirectoryService uds) { this.userDirectoryService = uds; } /** Injects the SessionManager into this service **/ public void setSessionManager(SessionManager sm) { this.sessionManager = sm; } /** Injects the TimeService into this service **/ public void setTimeService(TimeService ts) { this.timeService = ts; } /** Injects the SecurityService into this service **/ public void setSecurityService(SecurityService ss) { this.securityService = ss; } /** Injects the SiteService into this service **/ public void setSiteService(SiteService ss) { this.siteService = ss; } /**Injects the EventTrackingService into this service **/ public void setEventTrackingService(EventTrackingService ets) { this.eventTrackingService = ets; } /** Injects PodcastAuthzService into this service **/ public void setPodcastPermissionsService(PodcastPermissionsService podcastPermissionsService) { this.podcastPermissionsService = podcastPermissionsService; } /** * Retrieve the site id */ public String getSiteId() { return toolManager.getCurrentPlacement().getContext(); } /** * Retrieve the current user id */ public String getUserId() { return sessionManager.getCurrentSessionUserId(); } /** * Retrieve the current user display name */ public String getUserName() { return userDirectoryService.getCurrentUser().getDisplayName(); } /** * Returns the site URL as a string * * @return * String containing the sites URL */ public String getSiteURL() { return contentHostingService.getEntityUrl(siteRef); } /** * Returns only those podcasts whose DISPLAY_DATE property is today or earlier * * @param resourcesList * List of podcasts * * @return List * List of podcasts whose DISPLAY_DATE is today or before */ public List filterPodcasts(List resourcesList) { return filterPodcasts(resourcesList, getSiteId()); } /** * Returns only those podcasts whose DISPLAY_DATE property is today or earlier * This version for feed so can pass siteId * * @param resourcesList * List of podcasts * * @return List * List of podcasts whose DISPLAY_DATE is today or before */ public List filterPodcasts(List resourcesList, String siteId) { List filteredPodcasts = new ArrayList(); final Time now = timeService.newTime(); // loop to check if DISPLAY_DATE has been set. If not, set it final Iterator podcastIter = resourcesList.iterator(); ContentResource aResource = null; ResourceProperties itsProperties = null; // for each bean while (podcastIter.hasNext()) { // get its properties from ContentHosting aResource = (ContentResource) podcastIter.next(); itsProperties = aResource.getProperties(); try { Time podcastTime = aResource.getReleaseDate(); if (podcastTime == null) { podcastTime = itsProperties.getTimeProperty(DISPLAY_DATE); } // has it been published or does user have hidden permission if (podcastTime.getTime() <= now.getTime() || podcastPermissionsService.hasPerm(PodcastPermissionsService.HIDDEN_PERMISSIONS, retrievePodcastFolderId(siteId), siteId)) { // check if there is a retract date and if so, we have not // passed it final Time retractDate = aResource.getRetractDate(); if (retractDate == null || retractDate.getTime() >= now.getTime()) { filteredPodcasts.add(aResource); } } } catch (Exception e) { // catches EntityPropertyNotDefinedException, EntityPropertyTypeException // any problems, skip this one LOG.warn(e.getMessage() + " for podcast item: " + aResource.getId() + ". SKIPPING...", e); } } return filteredPodcasts; } /** * Get ContentCollection object for podcasts * * @param String * The siteId to grab the correct podcasts * * @return ContentCollection The ContentCollection object containing the * podcasts */ public ContentCollection getContentCollection(String siteId) throws IdUnusedException, PermissionException { ContentCollection collection = null; try { final String podcastsCollection = retrievePodcastFolderId(siteId); collection = contentHostingService.getCollection(podcastsCollection); } catch (TypeException e) { LOG.error("TypeException when trying to get podcast collection for site: " + siteId + ": " + e.getMessage(), e); throw new PodcastException(e); } catch (IdUnusedException e) { LOG.warn("IdUnusedException while attempting to get podcast collection. " + "for site: " + siteId + ". " + e.getMessage(), e); throw e; } catch (PermissionException e) { // catches PermissionException LOG.warn("PermissionException when trying to get podcast collection for site: " + siteId + ": " + e.getMessage(), e); // now group aware, so folder may be restricted to a group // so rethrow exception so UI can capture and deal with throw e; } return collection; } /** * Get ContentCollection object for podcasts * * @param String * The siteId to grab the correct podcasts * * @return ContentCollection * The ContentCollection object containing the podcasts */ public ContentCollectionEdit getContentCollectionEditable(String siteId) throws IdUnusedException, PermissionException, InUseException { ContentCollectionEdit collection = null; String podcastsCollection = ""; try { podcastsCollection = retrievePodcastFolderId(siteId); collection = contentHostingService.editCollection(podcastsCollection); } catch (TypeException e) { LOG.error("TypeException when trying to get podcast collection for site: " + siteId + ": " + e.getMessage(), e); throw new PodcastException(e); } catch (IdUnusedException e) { LOG.error("IdUnusedException when trying to get podcast collection for edit in site: " + siteId + " " + e.getMessage(), e); throw e; } catch (PermissionException e) { LOG.error("PermissionException when trying to get podcast collection for edit in site: " + siteId + " " + e.getMessage(), e); throw e; } catch (InUseException e) { LOG.warn("InUseException attempting to retrieve podcast folder " + podcastsCollection + " for site: " + siteId + ". " + e.getMessage(), e); throw e; } return collection; } /** * Returns a reference to Podcasts folder in Resources to pass to * permissions page. */ public String getPodcastsFolderRef() { try { ContentCollection podcastFolder = getContentCollection(getSiteId()); return podcastFolder.getReference(); } catch (Exception e) { LOG.error("Exception thrown while attempting to retrieve podcast folder reference.", e); } return null; } /** * Remove non-file resources from list of potential podcasts and * files restricted to groups user not part of * * @param resourcesList * The list of potential podcasts * * @return List * List of files to make up the podcasts */ public List filterResources(List resourcesList) { return filterResources(resourcesList, getSiteId()); } public List filterResources(List resourcesList, String siteId) { List filteredResources = new ArrayList(); ContentResource aResource = null; // is this user an instructor? // need to clear advisors so doesn't blindly return true boolean hadAdvisor = false; if (securityService.hasAdvisors()) { securityService.popAdvisor(); hadAdvisor = true; } final boolean canUpdateSite = podcastPermissionsService.canUpdateSite(siteId); if (hadAdvisor) enablePodcastSecurityAdvisor(); // loop to check if objects are collections (folders) or resources // (files) final Iterator podcastIter = resourcesList.iterator(); // for each bean while (podcastIter.hasNext()) { // get its properties from ContentHosting try { aResource = (ContentResource) podcastIter.next(); if (aResource.isResource()) { final boolean isGrouped = podcastPermissionsService.isGrouped(aResource); if ((! canUpdateSite) && isGrouped) { if (podcastPermissionsService.canAccessViaGroups(aResource.getGroups(), siteId)) { filteredResources.add(aResource); } } else { filteredResources.add(aResource); } } } catch (ClassCastException e) { LOG.info("Non-file resource in podcasts folder at site " + siteId + ", so ignoring. "); } } return filteredResources; } /** * Returns TRUE if the possible id is the correct one. If they * cannot access an Exception will be thrown. * Created for SAK-13740 * @param podcastCollection * @param siteId * @param isStudent * @return */ private boolean isPodcastsFolderId(String podcastsCollection, String siteId, boolean isStudent) throws TypeException, IdUnusedException, PermissionException { // SAK-13740: need to access folder to determine if folder is hidden BUT if user is student // and the folder is hidden, PermissionException thrown so SecurityAdvisor enabled. // Need to determine so a WARNing can be logged and not a PermissionException. if (isStudent) { enablePodcastSecurityAdvisor(); } ContentCollection podcastFolder = contentHostingService.getCollection(podcastsCollection); if (isStudent) { Date tempDate = null; if (podcastFolder.getRetractDate() != null) { tempDate = new Date(podcastFolder.getRetractDate().getTime()); } boolean result = podcastPermissionsService.isResourceHidden(podcastFolder, tempDate); securityService.popAdvisor(); if (result) { // a student/access user is attempting to access and the folder is 'hidden' so just log the // situation and return the String - what should happen sp don't print out stack trace LOG.warn("Podcasts folder " + podcastsCollection + " is HIDDEN, before RELEASE DATE, or " + "after RETRACT DATE so cannot access."); } else { // not hidden so check without SecurityAdvisor to see if we can access podcastFolder = contentHostingService.getCollection(podcastsCollection); } } return true; } /** * Returns podcast folder id using either 'podcasts' or 'Podcasts'. If it * does not exist in either form, will create it. * * @param siteId * The site to search * * @return String * Contains the complete id for the podcast folder * * @throws PermissionException * Access denied or Not found so not available * @throws IdInvalidException * Constructed Id not valid * @throws IdUsedException * When attempting to create Podcast folder, id is a duplicate */ public String retrievePodcastFolderId(String siteId) throws PermissionException { final String siteCollection = contentHostingService.getSiteCollection(siteId); String podcastsCollection = siteCollection + COLLECTION_PODCASTS + Entity.SEPARATOR; boolean isStudent = ! podcastPermissionsService.canUpdateSite(siteId); // Also refactored to streamline code. try { if (isPodcastsFolderId(podcastsCollection, siteId, isStudent)) { return podcastsCollection; } } catch (TypeException e1) { LOG.error("TypeException while trying to determine correct podcast folder Id String " + " for site: " + siteId + ". " + e1.getMessage(), e1); throw new PodcastException(e1); } catch (IdUnusedException e2) { // Podcasts is truly not the name of the folder, so drop through and try another podcastsCollection = siteCollection + COLLECTION_PODCASTS_ALT + Entity.SEPARATOR; // once again, since we are dealing with a student/access user, if folder is 'hidden' // this user can't access, so enable an advisor to determine if it truly does exist try { if (isPodcastsFolderId(podcastsCollection, siteId, isStudent)) { return podcastsCollection; } } catch (IdUnusedException e) { // SAK-19347 - removed unhelpful logs //LOG.warn("IdUnusedException while trying to determine correct podcast folder id " // + " for site: " + siteId + ". " + e.getMessage()); if (!siteHasTool(siteId, "sakai.podcasts")) { return null; } // if we get here it does not exist so create if (isStudent) { enablePodcastSecurityAdvisor(); } createPodcastsFolder(siteCollection + COLLECTION_PODCASTS + Entity.SEPARATOR, siteId); return siteCollection + COLLECTION_PODCASTS + Entity.SEPARATOR; } catch (TypeException e) { LOG.error("TypeException while trying to determine correct podcast folder Id String " + " for site: " + siteId + ". " + e.getMessage(), e); throw new PodcastException(e); } } finally { securityService.popAdvisor(); } return null; } /** * Determines whether the site has the specified tool. * * @param siteId * The site id to check * @param toolId * The tool id to check * @return True - if it has, false - otherwise */ private boolean siteHasTool(String siteId, String toolId) { boolean result = false; try { Site site = siteService.getSite(siteId); if (site.getToolForCommonId(toolId) != null) { result = true; } } catch (IdUnusedException e) { LOG.warn("IdUnusedException while trying to determine whether site " + siteId + " has tool " + toolId + ". " + e.getMessage()); } return result; } /** * Retrieve Podcasts for site and if podcast folder does not exist, create * it. Used within tool since siteId known * * @return List * A List of podcast resources */ public List getPodcasts() throws PermissionException, InUseException, IdInvalidException, InconsistentException, IdUsedException { return getPodcasts(getSiteId()); } /** * Retrieve Podcasts for site and if podcast folder does not exist, create * it. Used by feed since no context to pull siteId from * * @param String * The siteId the feed needs the podcasts from * * @return List * A List of podcast resources */ public List getPodcasts(String siteId) throws PermissionException, InUseException, IdInvalidException, InconsistentException,IdUsedException { List resourcesList = new ArrayList(); final String podcastsCollection = retrievePodcastFolderId(siteId); try { checkForFeedInfo(podcastsCollection, siteId); // Get podcasts folder collection from Resource for this site final ContentCollection collectionEdit = getContentCollection(siteId); // If not instructor, check if folder is restricted to group access // and if so, if this user does not have access, return empty List if (! podcastPermissionsService.canUpdateSite(siteId) && podcastPermissionsService.isGrouped(collectionEdit) && ! podcastPermissionsService.canAccessViaGroups(collectionEdit.getGroups(), siteId)) { return new ArrayList(); } resourcesList = collectionEdit.getMemberResources(); // remove non-file resources from collection as well as // those restricted to groups (if user non-instructor) resourcesList = filterResources(resourcesList, siteId); // if added from Resources will not have this property. // if not, this will call a method to set it. // returns the revised list of podcasts, suitable for framing (sorting) resourcesList = checkDISPLAY_DATE(resourcesList, siteId); // sort based on display (publish) date, most recent first PodcastComparator podcastComparator = new PodcastComparator( DISPLAY_DATE, false); Collections.sort(resourcesList, podcastComparator); } catch (IdUnusedException ex) { // Does not exist, attempt to create it if (podcastPermissionsService.canUpdateSite()) { createPodcastsFolder(podcastsCollection, siteId); } else { return new ArrayList(); } } return resourcesList; } /** * Pulls a ContentResource from ContentHostingService. * * @param String * The resourceId of the resource to get * * @return ContentResource * If found, null otherwise */ private ContentResource getAResource(String resourceId) throws PermissionException, IdUnusedException { ContentResource crEdit = null; try { crEdit = contentHostingService.getResource(resourceId); } catch (TypeException e) { LOG.error("TypeException while attempting to pull resource: " + resourceId + " for site: " + getSiteId() + ". " + e.getMessage(), e); throw new PodcastException(e); } return crEdit; } /** * Pulls a ContentResourceEdit from ContentHostingService. * * @param String * The resourceId of the resource to get * * @return ContentResourceEdit * If found, null otherwise */ private ContentResourceEdit getAResourceEdit(String resourceId) throws PermissionException, IdUnusedException { ContentResourceEdit crEdit = null; try { enablePodcastSecurityAdvisor(); crEdit = contentHostingService.editResource(resourceId); } catch (TypeException e) { LOG.error("TypeException while attempting to pull resource: " + resourceId + " for site: " + getSiteId() + ". " + e.getMessage(), e); throw new PodcastException(e); } catch (InUseException e) { // Weirdness, should not be in use return null; } finally{ securityService.popAdvisor(); } return crEdit; } /** * Add a podcast to the site's resources * * @param title * The title of this podcast resource * @param displayDate * The display date for this podcast resource * @param description * The description of this podcast resource * @param body * The bytes of this podcast * * @throws OverQuotaException * To display Over Quota Alert to user * @throws ServerOverloadException * To display Internal Server Error Alert to user * @throws InconsistentException * To display Internal Server Error Alert to user * @throws IdInvalidException * To display Invalid Id Alert to user * @throws IdLengthException * To display File path too long Alert to user * @throws PermissionException * To display Permission denied Alert to user * @throws IdUniquenessException * To display Duplicate id used Alert to user */ public void addPodcast(String title, Date displayDate, String description, byte[] body, String filename, String contentType) throws OverQuotaException, ServerOverloadException, InconsistentException, IdInvalidException, IdLengthException, PermissionException, IdUniquenessException { final int idVariationLimit = 100; // actually is checked against // if they need more than 100 copies, too bad final String resourceCollection = retrievePodcastFolderId(getSiteId()); String basename, extension = ""; int dot = filename.lastIndexOf('.'); if (dot != -1) { basename = filename.substring(0, dot); extension = filename.substring(dot); } else { basename = filename; } // Method: create a resource, fill in its properties, // commit to officially save it ContentResourceEdit cr = null; try { // create the initial object cr = contentHostingService.addResource(resourceCollection, basename, extension, idVariationLimit); } catch (IdUnusedException e) { LOG.error("IdUnusedException trying to add a podcast to Podcasts folder in Resources", e); throw new InconsistentException("Could not find the collection " + resourceCollection + " while attempting to " + "add the podcast " + filename); } // Add the actual contents of the file and content type cr.setContent(body); cr.setContentType(contentType); // fill up its properties final ResourcePropertiesEdit resourceProperties = cr.getPropertiesEdit(); resourceProperties.addProperty(ResourceProperties.PROP_IS_COLLECTION, Boolean.FALSE.toString()); resourceProperties.addProperty(ResourceProperties.PROP_DISPLAY_NAME, title); resourceProperties.addProperty(ResourceProperties.PROP_DESCRIPTION, description); final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmssSSS"); formatter.setTimeZone(timeService.getLocalTimeZone()); resourceProperties.addProperty(DISPLAY_DATE, formatter .format(displayDate)); cr.setReleaseDate(timeService.newTime(displayDate.getTime())); resourceProperties.addProperty(ResourceProperties.PROP_CONTENT_LENGTH, new Integer(body.length).toString()); // now to commit the changes contentHostingService.commitResource(cr, NotificationService.NOTI_NONE); // add entry for event tracking final Event event = eventTrackingService.newEvent(EVENT_ADD_PODCAST, getEventMessage(cr.getReference()), true, NotificationService.NOTI_NONE); eventTrackingService.post(event); } /** * Removes a podcast * * @param id * The podcast to be removed from ContentHosting */ public void removePodcast(String resourceId) throws IdUnusedException, InUseException, TypeException, PermissionException { ContentResourceEdit edit = null; edit = contentHostingService.editResource(resourceId); contentHostingService.removeResource(edit); // add entry for event tracking final Event event = eventTrackingService.newEvent(EVENT_DELETE_PODCAST, edit.getReference(), true, NotificationService.NOTI_NONE); eventTrackingService.post(event); } /** * Tests whether the podcasts folder exists and create it if it does not * * @return True - if exists, false - otherwise */ public boolean checkPodcastFolder() throws PermissionException, InUseException { return (retrievePodcastFolderId(getSiteId()) != null); } private boolean anyPodcastsVisible(List podcasts) { final List filteredPodcasts = filterResources(podcasts); return filteredPodcasts != null && ! filteredPodcasts.isEmpty(); } /** * Determines if folder contains actual files * * @return boolean true if files are stored there, false otherwise */ public boolean checkForActualPodcasts() { try { // if student/access user and folder exists but is hidden if (! podcastPermissionsService.canUpdateSite() && isPodcastFolderHidden(getSiteId()) ) { return false; } final String podcastsCollection = retrievePodcastFolderId(getSiteId()); if (podcastsCollection != null) { final ContentCollection collection = contentHostingService .getCollection(podcastsCollection); if (collection == null) { return false; } else { final List resourcesList = collection.getMemberResources(); if (resourcesList != null) { if (resourcesList.isEmpty()) return false; else if (podcastPermissionsService.canUpdateSite()) return true; else return anyPodcastsVisible(resourcesList); } else return false; } } } catch (Exception e) { // catches IdUnusedException, TypeException, PermissionException LOG.warn(e.getMessage() + " while checking for files in podcast folder: " + " for site: " + getSiteId() + ". " + e.getMessage(), e); } return false; } /** * Will apply changes made (if any) to podcast * * @param String * The resourceId * @param String * The title * @param Date * The display/publish date * @param String * The description * @param byte[] * The actual file contents * @param String * The filename */ public void revisePodcast(String resourceId, String title, Date date, String description, byte[] body, String filename) throws PermissionException, InUseException, OverQuotaException, ServerOverloadException { try { // get Resource to modify ContentResourceEdit podcastEditable = null; podcastEditable = contentHostingService.editResource(resourceId); final ResourcePropertiesEdit podcastResourceEditable = podcastEditable .getPropertiesEdit(); if (!title.equals(podcastResourceEditable .getProperty(ResourceProperties.PROP_DISPLAY_NAME))) { podcastResourceEditable .removeProperty(ResourceProperties.PROP_DISPLAY_NAME); podcastResourceEditable.addProperty( ResourceProperties.PROP_DISPLAY_NAME, title); } if (!description.equals(podcastResourceEditable .getProperty(ResourceProperties.PROP_DESCRIPTION))) { podcastResourceEditable .removeProperty(ResourceProperties.PROP_DESCRIPTION); podcastResourceEditable.addProperty( ResourceProperties.PROP_DESCRIPTION, description); } if (date != null) { podcastResourceEditable.removeProperty(DISPLAY_DATE); final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmssSSS"); formatter.setTimeZone(timeService.getLocalTimeZone()); podcastResourceEditable.addProperty(DISPLAY_DATE, formatter.format(date)); podcastEditable.setReleaseDate(timeService.newTime(date.getTime())); } // REMOVED SINCE IF FILENAME CHANGED, ENTIRELY NEW RESOURCE CREATED SO THIS CODE SHOULD NEVER BE EXECUTED /* if (!filename.equals(podcastResourceEditable.getProperty(ResourceProperties.PROP_ORIGINAL_FILENAME)))) { String oldFilename = podcastResourceEditable.getProperty(ResourceProperties.PROP_ORIGINAL_FILENAME); podcastResourceEditable.removeProperty(ResourceProperties.PROP_ORIGINAL_FILENAME); podcastResourceEditable.addProperty( ResourceProperties.PROP_ORIGINAL_FILENAME, Validator .escapeResourceName(filename)); podcastResourceEditable.removeProperty(ResourceProperties.PROP_CONTENT_LENGTH); podcastResourceEditable.addProperty(ResourceProperties.PROP_CONTENT_LENGTH, new Integer(body.length).toString()); // If title = filename, since filename changed, change title to match if (oldFilename == podcastResourceEditable.getProperty(ResourceProperties.PROP_DISPLAY_NAME)) { podcastResourceEditable.removeProperty(ResourceProperties.PROP_DISPLAY_NAME); podcastResourceEditable.addProperty(ResourceProperties.PROP_DISPLAY_NAME, Validator.escapeResourceName(filename)); } } */ // Set for no notification. TODO: when notification implemented, // need to revisit 2nd parameter. contentHostingService.commitResource(podcastEditable, NotificationService.NOTI_NONE); // add entry for event tracking Event event = eventTrackingService.newEvent(EVENT_REVISE_PODCAST, podcastEditable.getReference(), true); eventTrackingService.post(event); } catch (IdUnusedException e) { LOG.error(e.getMessage() + " while revising podcasts for site: " + getSiteId() + ". ", e); throw new PodcastException(e); } catch (TypeException e) { LOG.error(e.getMessage() + " while revising podcasts for site: " + getSiteId() + ". ", e); throw new PodcastException(e); } } /** * Checks if podcast resources have a DISPLAY_DATE set. Occurs when files * uploaded to podcast folder from Resources. * * @param List * The list of podcast resources * * @return List The list of podcast resource all with DISPLAY_DATE property */ public List checkDISPLAY_DATE(List resourcesList) { return checkDISPLAY_DATE(resourcesList, getSiteId()); } public List checkDISPLAY_DATE(List resourcesList, String siteId) { // recreate the list in case needed to // add DISPLAY_DATE to podcast(s) List revisedList = new ArrayList(); final Iterator podcastIter = resourcesList.iterator(); ContentResource aResource = null; ResourceProperties itsProperties= null; // for each bean // loop to check if DISPLAY_DATE has been set. If not, set it while (podcastIter.hasNext()) { aResource = (ContentResource) podcastIter.next(); final String id = aResource.getId(); // save id just in case modified and committed, need to get it back itsProperties = aResource.getProperties(); // get its properties from ContentHosting try { // Release/Retract dates implemented after Podcasts // so if null, need to check if old Podcast, ie uses // DISPLAY_DATE property. // Also, if hidden property set, release date becomes null. if (aResource.getReleaseDate() == null) { if (itsProperties.getProperty(DISPLAY_DATE) == null) { aResource = setDISPLAY_DATE(siteId, aResource.getId(), null); itsProperties = aResource.getProperties(); } if (! aResource.isHidden()) { setReleaseDate(siteId, aResource, itsProperties.getTimeProperty(DISPLAY_DATE)); try { aResource = getAResource(id); } catch (Exception e) { LOG.error(e); } } } else { if (itsProperties.getProperty(DISPLAY_DATE) == null) { aResource = setDISPLAY_DATE(siteId, aResource.getId(), null); itsProperties = aResource.getProperties(); } } } catch (EntityPropertyNotDefinedException e) { // DISPLAY_DATE does not exist, add it LOG.info("DISPLAY_DATE does not exist for " + aResource.getId() + " attempting to add."); try { aResource = setDISPLAY_DATE(siteId, aResource.getId(), null); if (aResource.getReleaseDate() == null && ! aResource.isHidden()) { if (! aResource.isHidden()) { setReleaseDate(siteId, aResource, itsProperties.getTimeProperty(DISPLAY_DATE)); aResource = getAResource(id); } } } catch (EntityPropertyTypeException e1) { // Weirdness, should have just set it LOG.debug("EntityPropertyTypeException while trying to set Release Date after" + " freshly setting DISPLAY_DATE", e1); } catch (EntityPropertyNotDefinedException e1) { // Weirdness, should have just set it LOG.debug("EntityPropertyNotDefinedException while trying to set Release Date after" + " freshly setting DISPLAY_DATE", e1); } catch (Exception e1) { // PermissionException, IdUnusedException from getAResource LOG.error(e1); } } catch (EntityPropertyTypeException e) { // not a file, skip over it LOG.debug("EntityPropertyTypeException while checking for DISPLAY_DATE. " + " Possible non-resource (aka a folder) exists in podcasts folder. " + "SKIPPING..." + e.getMessage(), e); } finally { securityService.popAdvisor(); } // aResource values properly set, so add to list revisedList.add(aResource); aResource = null; } return revisedList; } /** * Sets Release Date property of the podcast resource * * @param aResource * The ContentResource object of the podcast * * @param displayDate * The Time object the Release Date is set to */ private ContentResource setReleaseDate(ContentResource aResource, Time displayDate) { return setReleaseDate(getSiteId(), aResource, displayDate); } private ContentResource setReleaseDate(String siteId, ContentResource aResource, Time displayDate) { ContentResource refreshedResource = null; ContentResourceEdit aResourceEdit = null; try { aResourceEdit = getAResourceEdit(aResource.getId()); if (aResourceEdit.getReleaseDate() == null) { Time releaseDate = getDISPLAY_DATE(aResourceEdit.getPropertiesEdit()); aResourceEdit.setReleaseDate(releaseDate); contentHostingService.commitResource(aResourceEdit, NotificationService.NOTI_NONE); // add entry for event tracking final Event event = eventTrackingService.newEvent(EVENT_REVISE_PODCAST, getEventMessage(aResourceEdit.getProperties().getProperty(ResourceProperties.PROP_DISPLAY_NAME), siteId), true, NotificationService.NOTI_NONE); eventTrackingService.post(event); } } catch (Exception e1) { // catches PermissionException IdUnusedException // TypeException InUseException LOG.error("Problem getting resource for editing while trying to set DISPLAY_DATE for site " + siteId + ". ", e1); if (aResourceEdit != null) { contentHostingService.cancelResource(aResourceEdit); } } try { refreshedResource = getAResource(aResource.getId()); } catch (Exception e) { // Weirdness since we just used this to update its release date LOG.error("Problem retrieving updated podcast resource after adding release date.", e); } return refreshedResource; } /** * Sets the DISPLAY_DATE property to the creation date of the podcast. * Needed if file added using Resources. Time stored is GMT so when pulled * need to convert to local. * * @param ResourceProperties * The ResourceProperties that need DISPLAY_DATE added */ public ContentResource setDISPLAY_DATE(String resourceId, Time releaseDate) { return setDISPLAY_DATE(getSiteId(), resourceId, releaseDate); } public ContentResource setDISPLAY_DATE(String siteId, String resourceId, Time releaseDate) { ContentResource refreshedResource = null; final SimpleDateFormat formatterProp = new SimpleDateFormat("yyyyMMddHHmmssSSS"); Date tempDate = null; try { ContentResourceEdit aResource = getAResourceEdit(resourceId); ResourceProperties rp = aResource.getProperties(); // Convert GMT time stored by Resources into local time if (releaseDate == null) { tempDate = formatterProp.parse(rp.getTimeProperty( ResourceProperties.PROP_MODIFIED_DATE).toString()); } else { tempDate = new Date(releaseDate.getTime()); } rp.addProperty(DISPLAY_DATE, formatterProp.format(tempDate)); contentHostingService.commitResource(aResource, NotificationService.NOTI_NONE); // add entry for event tracking final Event event = eventTrackingService.newEvent(EVENT_REVISE_PODCAST, getEventMessage(aResource.getProperties().getProperty(ResourceProperties.PROP_DISPLAY_NAME), siteId), true, NotificationService.NOTI_NONE); eventTrackingService.post(event); } catch (Exception e) { // catches EntityPropertyNotDefinedException // EntityPropertyTypeException, ParseException LOG.error(e.getMessage() + " while setting DISPLAY_DATE for " + "file in site " + siteId + ". " + e.getMessage(), e); throw new PodcastException(e); } try { refreshedResource = getAResource(resourceId); } catch (Exception e) { // Weirdness since we just used this to update its release date LOG.error("Problem retrieving updated podcast resource after adding release date.", e); } return refreshedResource; } /** * If Release Date property not set, check if DISPLAY_DATE exists * and if it does, return it so it can be set as the Release Date. * If DISPLAY_DATE does not exist, default to last modified date. * * @param ResourceProperties * The ResourceProperties to get DISPLAY_DATE from */ public Time getDISPLAY_DATE(ResourceProperties rp) { final SimpleDateFormat formatterProp = new SimpleDateFormat("yyyyMMddHHmmssSSS"); formatterProp.setTimeZone(timeService.getLocalTimeZone()); Date tempDate = null; try { // Convert GMT time stored by Resources into local time tempDate = formatterProp.parse(rp.getTimeProperty(DISPLAY_DATE).toStringLocal()); return timeService.newTime(tempDate.getTime()); } catch (Exception e) { try { tempDate = formatterProp.parse(rp.getTimeProperty( ResourceProperties.PROP_MODIFIED_DATE).toStringLocal()); return timeService.newTime(tempDate.getTime()); } catch (Exception e1) { // catches EntityPropertyNotDefinedException // EntityPropertyTypeException, ParseException LOG.info(e1.getMessage() + " while getting DISPLAY_DATE for " + "file in site " + getSiteId() + ". ", e); } } return null; } /** * Checks if podcast feed title and description exists and if not, add them * * @param podcastsCollection * The id for the podcasts collection * * @param siteId * The site id */ private void checkForFeedInfo(String podcastsCollection, String siteId) { try { final ContentCollection podcasts = contentHostingService.getCollection(podcastsCollection); final ResourceProperties rp = podcasts.getProperties(); final String podfeedTitle = rp.getProperty(PODFEED_TITLE); if (podfeedTitle == null) { // Podfeed Title does not exist, so add it final ContentCollectionEdit podcastsEdit = contentHostingService.editCollection(podcastsCollection); final ResourcePropertiesEdit resourceProperties = podcastsEdit.getPropertiesEdit(); resourceProperties.addProperty(ResourceProperties.PROP_DISPLAY_NAME, COLLECTION_PODCASTS_TITLE); resourceProperties.addProperty(ResourceProperties.PROP_DESCRIPTION, COLLECTION_PODCASTS_DESCRIPTION); try { // Set default feed title and description resourceProperties.addProperty(PODFEED_TITLE, siteService.getSite(siteId).getTitle() + "'s Official Podcasts"); final String feedDescription = "This is the official podcast for course " + siteService.getSite(siteId).getTitle() + ". Please check back throughout the semester for updates."; resourceProperties.addProperty(PODFEED_DESCRIPTION, feedDescription); commitContentCollection(podcastsEdit); } catch (IdUnusedException e) { LOG.error("IdUnusedException attempting to get site info to set feed title and description for site " + siteId, e); } } } catch (IdUnusedException e) { LOG.error("IdUnusedException attempting to retrive podcast folder collection to check " + "if feed info exists for site " + siteId, e); } catch (TypeException e) { LOG.error("TypeException attempting to retrive podcast folder collection to check " + "if feed info exists for site " + siteId, e); } catch (PermissionException e) { LOG.error("PermissionException attempting to retrive podcast folder collection to check " + "if feed info exists for site " + siteId, e); } catch (InUseException e) { LOG.info("InUsedException attempting to retrive podcast folder collection to check " + "if feed info exists for site " + siteId, e); } } /** * Returns the file's URL * * @param String * The resource Id * * @return String The URL for the resource */ public String getPodcastFileURL(String resourceId) throws PermissionException, IdUnusedException { String Url = null; try { Url = contentHostingService.getResource(resourceId).getUrl(); } catch (TypeException e) { LOG.error("TypeException while getting the resource " + resourceId + "'s URL. Resource from site " + getSiteId() + ". " + e.getMessage(), e); throw new PodcastException(e); } return Url; } /** * FUTURE: needed to implement Notification services * */ public void init() { checkSet(contentHostingService, "contentHostingService"); checkSet(eventTrackingService, "eventTrackingService"); checkSet(podcastPermissionsService, "podcastPermissionService"); checkSet(securityService, "securityService"); checkSet(sessionManager, "sessionManager"); checkSet(siteService, "siteService"); checkSet(timeService, "timeService"); checkSet(toolManager, "toolManager"); checkSet(userDirectoryService, "userDirectoryService"); /* EntityManager.registerEntityProducer(this, REFERENCE_ROOT); m_relativeAccessPoint = REFERENCE_ROOT; NotificationEdit edit = notificationService.addTransientNotification(); edit.setFunction(EVENT_PODCAST_ADD); edit.addFunction(EVENT_PODCAST_REVISE); edit.setResourceFilter(getAccessPoint(true)); edit.setAction(new SiteEmailNotificationPodcasts()); */ } public void destroy() { } /** * Changes the podcast folder view status (either PUBLIC or SITE) * * @param boolean * True means PUBLIC view, FALSE means private */ public void reviseOptions(boolean option) { String podcastsCollection = null; try { podcastsCollection = retrievePodcastFolderId(getSiteId()); ContentCollectionEdit collection = null; try { collection = contentHostingService.editCollection(podcastsCollection); // We don't use setPubView as it doesn't clear any existing // groups on the collection. if (option) { collection.setPublicAccess(); } else { collection.clearPublicAccess(); } contentHostingService.commitCollection(collection); } catch (Exception e) { LOG.warn("Failed to update access for collection: "+ podcastsCollection); throw new PodcastException(e); } finally { if (collection != null && collection.isActiveEdit()) { contentHostingService.cancelCollection(collection); } } } catch (PermissionException e) { LOG.warn("PermissionException attempting to retrieve podcast folder id " + " for site " + getSiteId() + ". " + e.getMessage(), e); throw new PodcastException(e); } } /** * Returns (from content hosting) whether the podcast folder is PUBLIC or * SITE * * @return int * 0 = Display to non-members, 1 = Display to Site */ public int getOptions() { String podcastsCollection = null; try { podcastsCollection = retrievePodcastFolderId(getSiteId()); if (isPublic(podcastsCollection)) { return PUBLIC; } else { return SITE; } } catch (PermissionException e) { LOG.warn("PermissionException attempting to retrieve podcast folder id " + " for site " + getSiteId() + ". " + e.getMessage(), e); throw new PodcastException(e); } } /** * Commits changes to ContentHosting (releases the lock) * * @param ContentCollectionEdit * The ContentCollection to be saved */ public void commitContentCollection(ContentCollectionEdit cce) { contentHostingService.commitCollection(cce); } /** * Cancels attempt at changing this collection (releases the lock) * * @param cce * The ContentCollectionEdit that is not to be changed */ public void cancelContentCollection(ContentCollectionEdit cce) { contentHostingService.cancelCollection(cce); } /** * Returns boolean TRUE = Display to non-members FALSE - Display to Site * * @param podcastFolderId * The podcast folder id to check * * @return boolean * TRUE - Display to non-members, FALSE - Display to Site */ public boolean isPublic(String podcastFolderId) { return contentHostingService.isPubView(podcastFolderId); } /** * Returns TRUE if Podcasts folder has HIDDEN property set * OR release date is in the future * OR retract date is in the past * * 05/08 - enable security advisor since if folder is truly * hidden and a student/access user attempts to access it will * generate a Permissions error */ public boolean isPodcastFolderHidden(String siteId) throws IdUnusedException, PermissionException { enablePodcastSecurityAdvisor(); ContentCollection podcastFolder = getContentCollection(siteId); Date tempDate = null; if (podcastFolder.getReleaseDate() != null) { tempDate = new Date(podcastFolder.getReleaseDate().getTime()); } boolean result = podcastPermissionsService.isResourceHidden(podcastFolder, tempDate); securityService.popAdvisor(); return result; } /** * Creates the podcasts folder in Resources * * @param podcastsCollection * The id to be used for the podcasts folder * * @param siteId * The site id for whom the folder is to be created */ private void createPodcastsFolder(String podcastsCollection, String siteId) { try { LOG.info("Could not find podcast folder, attempting to create."); ContentCollectionEdit collection = contentHostingService.addCollection(podcastsCollection); final ResourcePropertiesEdit resourceProperties = collection.getPropertiesEdit(); resourceProperties.addProperty(ResourceProperties.PROP_DISPLAY_NAME, COLLECTION_PODCASTS_TITLE); resourceProperties.addProperty(ResourceProperties.PROP_DESCRIPTION, COLLECTION_PODCASTS_DESCRIPTION); // Set default feed title and description resourceProperties.addProperty(PODFEED_TITLE, siteService.getSite(siteId).getTitle() + getMessageBundleString(FEED_TITLE_STRING)); final String feedDescription = siteService.getSite(siteId).getTitle() + getMessageBundleString(FEED_DESC1_STRING) + getMessageBundleString(FEED_DESC2_STRING); resourceProperties.addProperty(PODFEED_DESCRIPTION, feedDescription); contentHostingService.commitCollection(collection); contentHostingService.setPubView(collection.getId(), true); } catch (Exception e) { // catches IdUnusedException, TypeException // InconsistentException, IdUsedException // IdInvalidException PermissionException // InUseException LOG.error(e.getMessage() + " while attempting to create Podcasts folder: " + " for site: " + siteId + ". NOT CREATED... " + e.getMessage(), e); throw new PodcastException(e); } } /** * Returns the date set in GMT time * * @param date * The date represented as a long value * * @return Date * The Date object set in GMT time */ public Date getGMTdate(long date) { final Calendar cal = Calendar.getInstance(timeService.getLocalTimeZone()); cal.setTimeInMillis(date); int gmtoffset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET); return new Date(date - gmtoffset); } /** * Generates a message for EventTracking * * @param object * The object that is part of the event * * @return * The String to be used to post the event */ private String getEventMessage(Object object) { return getEventMessage(object, getSiteId()); } private String getEventMessage(Object object, String siteId) { return "/content/group/" + siteId + "/Podcasts/" + getCurrentUser() + Entity.SEPARATOR + object.toString(); } private String getCurrentUser() { return sessionManager.getCurrentSessionUserId(); } /** * Determines if authenticated user has 'read' access to podcast collection folder * * @param id * The id for the podcast collection folder * * @return * TRUE - has read access, FALSE - does not */ public boolean allowAccess(String id) { return podcastPermissionsService.allowAccess(id); } /** * Sets the Faces error message by pulling the message from the * MessageBundle using the name passed in * * @param key * The name in the MessageBundle for the message wanted * * @return String * The string that is the value of the message */ private String getMessageBundleString(String key) { return resbud.getString(key); } /** * Establish a security advisor to allow the "embedded" azg work to occur * with no need for additional security permissions. * Kept here since don't want to make public. */ protected void enablePodcastSecurityAdvisor() { // put in a security advisor so we can do our podcast work without need // of further permissions securityService.pushAdvisor(new SecurityAdvisor() { public SecurityAdvice isAllowed(String userId, String function, String reference) { return SecurityAdvice.ALLOWED; } }); } public boolean allowOptions(int option) { if (option == SITE) { try{ String folderId = retrievePodcastFolderId(getSiteId()); ContentCollection parentCollection = contentHostingService.getCollection(folderId).getContainingCollection(); return !contentHostingService.isPubView(parentCollection.getId()); }catch(IdUnusedException e){ LOG.error("Shouldn't happen as folder should have already been created.", e); }catch(PermissionException e){ LOG.error("Shouldn't happen as folder should have already been created correctly.", e); }catch(TypeException e){ LOG.error("Shouldn't happen as folder should have already been created correctly.", e); } } else if (option == PUBLIC) { // No special rules at the moment. } return true; } }