/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/msgcntr/trunk/messageforums-app/src/java/org/sakaiproject/tool/messageforums/ui/MessageForumsSynopticBean.java $ * $Id: MessageForumsSynopticBean.java $ *********************************************************************************** * * 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.tool.messageforums.ui; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.api.app.messageforums.Area; import org.sakaiproject.api.app.messageforums.AreaManager; import org.sakaiproject.api.app.messageforums.DiscussionForum; import org.sakaiproject.api.app.messageforums.DiscussionTopic; import org.sakaiproject.api.app.messageforums.DiscussionForumService; import org.sakaiproject.api.app.messageforums.MessageForumsForumManager; import org.sakaiproject.api.app.messageforums.MessageForumsMessageManager; import org.sakaiproject.api.app.messageforums.MessageForumsTypeManager; import org.sakaiproject.api.app.messageforums.PrivateForum; import org.sakaiproject.api.app.messageforums.PrivateMessage; import org.sakaiproject.api.app.messageforums.Topic; import org.sakaiproject.api.app.messageforums.UniqueArrayList; import org.sakaiproject.api.app.messageforums.ui.DiscussionForumManager; import org.sakaiproject.api.app.messageforums.ui.PrivateMessageManager; import org.sakaiproject.api.app.messageforums.ui.UIPermissionsManager; import org.sakaiproject.component.app.messageforums.dao.hibernate.PrivateTopicImpl; import org.sakaiproject.component.cover.ServerConfigurationService; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.ToolManager; import org.sakaiproject.tool.messageforums.PrivateMessagesTool; import org.sakaiproject.user.api.Preferences; import org.sakaiproject.user.api.PreferencesService; public class MessageForumSynopticBean { /** * Used to store synoptic information for a users unread messages. * Whether on the Home page of a site or in MyWorkspace determines * what properties are filled. * <p> * If in MyWorkspace, each object contains the number of unread * Private Messages and number of unread Discussion Forum messages. * </p> * <p> * If in the Home page of a site, each object contains either the * number of unread Private Messages or number of unread Discussion * Forum messages.</p> * * @author josephrodriguez * */ public class DecoratedCompiledMessageStats { private String siteName; private String siteId; /** MyWorkspace information */ private int unreadPrivateAmt; private int unreadForumsAmt; private String mcPageURL; private String privateMessagesUrl; private boolean messagesandForums; private boolean messages; private boolean forums; public String getSiteName() { return siteName; } public void setSiteName(String siteName) { this.siteName = siteName; } public int getUnreadPrivateAmt() { return unreadPrivateAmt; } public void setUnreadPrivateAmt(int unreadPrivateAmt) { this.unreadPrivateAmt = unreadPrivateAmt; } public int getUnreadForumsAmt() { return unreadForumsAmt; } public void setUnreadForumsAmt(int unreadForumsAmt) { this.unreadForumsAmt = unreadForumsAmt; } public String getMcPageURL() { return mcPageURL; } public void setMcPageURL(String mcPageURL) { this.mcPageURL = mcPageURL; } public String getSiteId() { return siteId; } public void setSiteId(String siteId) { this.siteId = siteId; } public String getPrivateMessagesUrl() { return privateMessagesUrl; } public void setPrivateMessagesUrl(String privateMessagesUrl) { this.privateMessagesUrl = privateMessagesUrl; } public boolean isMessagesandForums() { return messagesandForums; } public void setMessagesandForums(boolean messagesandForums) { this.messagesandForums = messagesandForums; } public boolean isMessages() { return messages; } public void setMessages(boolean messages) { this.messages = messages; } public boolean isForums() { return forums; } public void setForums(boolean forums) { this.forums = forums; } } /* =========== End of DecoratedCompiledMessageStats =========== */ // transient only persists in request scope private transient Boolean myWorkspace = null; private transient Boolean pmEnabled = null; private transient Boolean anyMFToolInSite = null; private transient List myWorkspaceContents = null; private transient String pvtTopicMessageUrl = null; private transient List groupedSitesCounts = null; private transient List userRoles = null; private transient List multiMembershipSites = null; private transient List compiledDFMessageCounts = null; private transient Map receivedFolderUuidByContextId = null; private transient List siteList = null; private transient Map sitesMap = null; private transient Map currentUserMembershipsBySite = null; private transient Map mfPageInSiteMap = null; // transient variable for when on home page of site private transient DecoratedCompiledMessageStats siteContents; /** Used to get contextId when tool on MyWorkspace to set all private messages to Read status */ private final String CONTEXTID="contextId"; /** Used to retrieve non-notification sites for MyWorkspace page */ private static final String TABS_EXCLUDED_PREFS = "sakai:portal:sitenav"; private final String TAB_EXCLUDED_SITES = "exclude"; /** Preferences service (injected dependency) */ protected PreferencesService preferencesService = null; /** =============== Main page bean values =============== */ /** Used to determine if there are sites to display on page */ private boolean sitesToView; private boolean sitesToViewSet = false; /** to get accces to log file */ private static final Log LOG = LogFactory.getLog(MessageForumSynopticBean.class); /** Needed if within a site so we only need stats for this site */ private MessageForumsMessageManager messageManager; /** Needed to grab unread counts for sites current user has group membership in */ private MessageForumsForumManager forumsManager; /** Needed to get topics if tool within a site */ private DiscussionForumManager forumManager; /** Needed to grab unread message count if tool within site */ private PrivateMessageManager pvtMessageManager; /** Needed to get forum message counts as well as Uuids for private messages and discussions */ private MessageForumsTypeManager typeManager; /** Needed to set up the counts for the private messages and forums */ private AreaManager areaManager; /** Needed to determine if user has read permission of topic */ private UIPermissionsManager uiPermissionsManager; public void setMessageManager(MessageForumsMessageManager messageManager) { this.messageManager = messageManager; } public void setForumsManager(MessageForumsForumManager forumsManager) { this.forumsManager = forumsManager; } public void setForumManager(DiscussionForumManager forumManager) { this.forumManager = forumManager; } public void setPvtMessageManager(PrivateMessageManager pvtMessageManager) { this.pvtMessageManager = pvtMessageManager; } public void setTypeManager(MessageForumsTypeManager typeManager) { this.typeManager = typeManager; } public void setAreaManager(AreaManager areaManager) { this.areaManager = areaManager; } public void setUiPermissionsManager(UIPermissionsManager uiPermissionsManager) { this.uiPermissionsManager = uiPermissionsManager; } public void setPreferencesService(PreferencesService preferencesService) { this.preferencesService = preferencesService; } /** * Returns TRUE if on MyWorkspace, FALSE if on a specific site * * @return * TRUE if on MyWorkspace, FALSE if on a specific site */ public boolean isMyWorkspace() { // myWorkspace is a transient variable if (myWorkspace == null) { // get context id final String siteId = getContext(); if (SiteService.getUserSiteId("admin").equals(siteId)) return false; myWorkspace = SiteService.isUserSite(siteId); LOG.debug("Result of determining if My Workspace: " + myWorkspace); } return myWorkspace.booleanValue(); } /** * Returns TRUE if there is at least one site user can access * and user has not set it to be excluded * * @return * TRUE if there are/is site(s) user can access * FALSE if not */ public boolean isSitesToView() { if (sitesToViewSet) { return sitesToView; } else { return ! filterOutExcludedSites(getSiteList()).isEmpty(); } } /** * @return * TRUE if (Private) Messages enabled, FALSE otherwise */ public boolean isPmEnabled() { if (pmEnabled == null) { final Area area = pvtMessageManager.getPrivateMessageArea(); pmEnabled = (area != null) && area.getEnabled().booleanValue(); } return pmEnabled; } /** * @return * DecoratedCompiledMessageStats for a single site */ public DecoratedCompiledMessageStats getSiteInfo() { return getSiteContents(); } /** * Need to grab the correct count for the role user has for each site. * Use for sites where user is not part of any group within a site. */ private List filterMessageCountsByRole(List dfCounts) { if (dfCounts.isEmpty()) { return dfCounts; } List resultList = new ArrayList(); List roles; for (Iterator dfCountIter = dfCounts.iterator(); dfCountIter.hasNext();) { final Object [] aCount = (Object []) dfCountIter.next(); roles = getCurrentUserMembershipsBySite((String) aCount[0]); String roleId = ((String) roles.get(0)); if (roleId.equals((String) aCount[1])) { resultList.add(aCount); } } return resultList; } /** * Removes from message counts messages that the user currently * does not have read access to. * * @param currentList * List of message counts * * @param removeList * List of messages that user actually does not have * read access to * @return * List with the adjusted message counts */ private List filterNoAccessMessages(List currentList, List removeList) { final List resultList = new ArrayList(); // ****** if either list is empty, return currentList unchanged ****** if (currentList.isEmpty() || removeList.isEmpty()) { return currentList; } // ****** Set up our iterator ****** final Iterator currentIter = currentList.iterator(); while (currentIter.hasNext()) { final Object [] resultValues = new Object [3]; Object [] removeValues; Object [] currentValues = null; // get current values for this iteration if (currentIter.hasNext()) { currentValues = (Object []) currentIter.next(); } if (currentValues == null) throw new IllegalStateException("currentValues == null"); // is current site in the removeList. if so, return index where final int pos = indexOf((String) currentValues[0], getSiteIds(removeList)); // if there are messages to remove, do so otherwise just add current values if (pos != -1) { removeValues = (Object []) removeList.get(pos); if (((String) currentValues[1]).equals(removeValues[1])) { resultValues[0] = currentValues[0]; resultValues[2] = Integer.valueOf( ((Integer) currentValues[2]).intValue() - ((Integer) removeValues[2]).intValue() ); } resultList.add(resultValues); removeList.remove(pos); } else { resultList.add(currentValues); } } return resultList; } /** * Returns a List of all roles a user has for all sites * they are a member of * * @param siteList * The List of site ids the user is a member of * * @return * List of role ids user has for all sites passed in */ private List getUserRoles(List siteList) { if (userRoles == null) { userRoles = new UniqueArrayList(); final Iterator siteIter = siteList.iterator(); List roles; while (siteIter.hasNext()) { String siteId = (String) siteIter.next(); roles = getCurrentUserMembershipsBySite(siteId); for (Iterator i = roles.iterator(); i.hasNext();) { String roleGroupName = (String) i.next(); if (! userRoles.contains(roleGroupName)) { userRoles.addAll(roles); } } } } return userRoles; } /** * For this particular site, pick the correct role's count * that needs to be removed from the total count * * @param removeMessageCounts * List of counts to be removed ordered by site id * * @param siteList * List of sites this user is a member of * * @return * List of correct counts, at most one per site */ private List selectCorrectRemoveMessageCount(List removeMessageCounts, List siteList) { // if message counts empty, nothing to do so return if (removeMessageCounts.isEmpty()) { return removeMessageCounts; } Object [] resultSet = null; final List resultList = new ArrayList(); final Iterator siteIter = siteList.iterator(); while (siteIter.hasNext()) { final String siteId = (String) siteIter.next(); // does current site contain counts to remove. if so, return index where // once processed, site id removed, so regenerate site list int pos = indexOf(siteId, getSiteIds(removeMessageCounts)); // found, so get it and add to result list if (pos != -1) { resultSet = (Object []) removeMessageCounts.get(pos); while (siteId.equals((String) resultSet[0])) { // permissions based on roles, so need to check if user's role has messages // that need to be removed from totals (either total or unread) List roleAndGroups = getCurrentUserMembershipsBySite(siteId); if (roleAndGroups.contains((String) resultSet[1])) { resultList.add(resultSet); } removeMessageCounts.remove(pos++); pos = indexOf(siteId, getSiteIds(removeMessageCounts)); if (pos != -1) { resultSet = (Object []) removeMessageCounts.get(pos); } else { // nope, no more for this site so do this to stop loop resultSet = new Object [3]; resultSet[0] = ""; } // end if setting up for next iteration of while } // end while (site id = remove message site id) } // end if (pos != -1) } // end while (sites to check) return resultList; } /** * Returns a list of all sites that the current user has * multiple memberships (ie, grouped). */ private List getMultiMembershipSites(List siteList) { if (multiMembershipSites == null) { multiMembershipSites = new ArrayList(); for (Iterator siteIter = siteList.iterator(); siteIter.hasNext();) { final String siteId = (String) siteIter.next(); List roles = getCurrentUserMembershipsBySite(siteId); if (roles.size() > 1) { multiMembershipSites.add(siteId); } } } return multiMembershipSites; } private List getCurrentUserMembershipsBySite(String siteId) { if (currentUserMembershipsBySite == null) { currentUserMembershipsBySite = new HashMap(); } List roles; if ((roles = (List) currentUserMembershipsBySite.get(siteId)) == null) { currentUserMembershipsBySite.put(siteId, uiPermissionsManager.getCurrentUserMemberships(siteId)); } return (List) currentUserMembershipsBySite.get(siteId); } private List filterAndAggragateGroupCounts(List counts) { if (counts.isEmpty()) { return counts; } List results = new ArrayList(); Object [] anotherCount; Iterator countIter = counts.iterator(); Object [] aCount = (Object []) countIter.next(); int forumCount = ((Integer) aCount[3]).intValue(); Long currentTopicId = (Long) aCount[1]; String currentContextId = (String) aCount[0]; String oldContextId; List currentUserMemberships = (List) getCurrentUserMembershipsBySite(currentContextId); while (countIter.hasNext()) { anotherCount = (Object []) countIter.next(); // if still in current site, add this count if (currentContextId.equals((String) anotherCount[0])) { if (currentTopicId.longValue() != ((Long) anotherCount[1]) && currentUserMemberships.contains((String) anotherCount[2]) ) { forumCount += ((Integer) anotherCount[3]).intValue(); currentTopicId = (Long) anotherCount[1]; } } else { // new site, save final count Object [] finalCount = new Object [2]; finalCount[0] = currentContextId; finalCount[1] = forumCount; results.add(finalCount); // set up for new site forumCount = ((Integer) anotherCount[3]).intValue(); currentTopicId = (Long) anotherCount[1]; oldContextId = currentContextId; currentContextId = (String) anotherCount[0]; } } // last set needs to be saved into list Object [] finalCount = new Object [2]; finalCount[0] = currentContextId; finalCount[1] = forumCount; results.add(finalCount); return results; } /** * Returns a count whose context id (index 0) matches the * value passed in. Otherwise null is returned. */ private Object [] getReadCount(List counts, String contextId) { for (Iterator countsIter = counts.iterator(); countsIter.hasNext();) { Object [] count = (Object []) countsIter.next(); if (contextId.equals((String) count [0])) { // we found the count, remove from list for efficiency (?) /* while (contextId.equals((String) count [0])) { counts.remove(count); if (countsIter.hasNext()) { count = (Object []) countsIter.next(); } else { count = new Object [2]; count[0] = ""; } } */ return count; } } return null; } /** * Returns the unread message count for each site. */ private List computeGroupedSitesUnreadCounts(List totalCounts, List readCounts) { if (readCounts.isEmpty()) { return totalCounts; } List results = new ArrayList(); for (Iterator totalCountsIter = totalCounts.iterator(); totalCountsIter.hasNext();) { Object [] totalCount = (Object []) totalCountsIter.next(); Object [] readCount = getReadCount(readCounts, (String) totalCount[0]); if (readCount == null) { results.add(totalCount); } else { Object [] finalCount = new Object [2]; finalCount[0] = totalCount[0]; finalCount[1] = ((Integer) totalCount[1]).intValue() - ((Integer) readCount[1]).intValue(); results.add(finalCount); } } return results; } /** * Returns unread message counts for sites this user is a * member of a group. Need this since memberships caused * overlapping access and counts too high. */ private List getGroupedSitesCounts(List groupedSites) { if (groupedSitesCounts == null) { if (groupedSites.isEmpty()) { groupedSitesCounts = groupedSites; } else { List results; final List roleList = getUserRoles(groupedSites); List dfTopicCounts = messageManager.findDiscussionForumMessageCountsForGroupedSitesByTopic(groupedSites, roleList); List dfTopicReadCounts = messageManager.findDiscussionForumReadMessageCountsForGroupedSitesByTopic(groupedSites, roleList); dfTopicCounts = filterAndAggragateGroupCounts(dfTopicCounts); dfTopicReadCounts = filterAndAggragateGroupCounts(dfTopicReadCounts); groupedSitesCounts = computeGroupedSitesUnreadCounts(dfTopicCounts, dfTopicReadCounts); } } return groupedSitesCounts; } /** * Computes the counts for sites with topics that don't have * membership items (permissions) associate with them, ie, it * uses the defaults * @param siteList * @return */ private List<Object []> computeNonMICounts(List<String> siteList) { // Get List of total counts - kept as list since will // be accessing all elements final List<Object []> nonMICounts = messageManager. findDiscussionForumMessageCountsForTopicsWithMissingPermsForAllSites(siteList); // Convert List of counts of read messages to Map since // will be accessing specific sites' counts final List<Object []> nonMIReadCounts = messageManager. findDiscussionForumReadMessageCountsForTopicsWithMissingPermsForAllSites(siteList); Map<String, Integer> nonMIReadCountsMap = new HashMap<String, Integer>(); for (Object [] nonMIReadCount: nonMIReadCounts) { nonMIReadCountsMap.put((String) nonMIReadCount[0], (Integer) nonMIReadCount[1]); } // Loop through all elements of nonMICounts and // do the actual computation if read count exists for current site for (Object [] nonMIcount: nonMICounts) { Integer nonMIReadCount = nonMIReadCountsMap.get((String) nonMIcount[0]); if (nonMIReadCount != null) { // Need to subtract int values, not Integer nonMIcount[1] = Integer.valueOf(((Integer) nonMIcount[1]).intValue() - nonMIReadCount.intValue()); } } return nonMICounts; } /** * Determines the number of unread messages for each site. * Filters out messages user does not have read permission for. * * @param siteList * List of sites user is a member of/has access to * * @return * List of unread message counts grouped by site */ private List compileDFMessageCount(List siteList) { if (compiledDFMessageCounts == null) { compiledDFMessageCounts = new ArrayList(); // retrieve what possible roles user could be in sites final List roleList = getUserRoles(siteList); // retrieve counts for topics within sites that don't // have membership item permissions saved in the db final List<Object []> nonMICounts = computeNonMICounts(siteList); final List siteListMinusGrouped = new ArrayList(); siteListMinusGrouped.addAll(siteList); // get List of sites where user is part of a group since // need to process differently since could affect which messages // able to view. final List groupedSites = getMultiMembershipSites(siteList); siteListMinusGrouped.removeAll(groupedSites); final List groupedSitesCounts = getGroupedSitesCounts(groupedSites); // ******* Pulls total discussion forum message counts from DB ******* // If grouped in all sites, no processing needed if (! siteListMinusGrouped.isEmpty()) { List discussionForumMessageCounts = messageManager .findDiscussionForumMessageCountsForAllSitesByPermissionLevelId(siteListMinusGrouped, roleList); discussionForumMessageCounts.addAll(messageManager .findDiscussionForumMessageCountsForAllSitesByPermissionLevelName(siteListMinusGrouped, roleList)); discussionForumMessageCounts = filterMessageCountsByRole(discussionForumMessageCounts); // if still messages, keep processing if (! discussionForumMessageCounts.isEmpty()) { // Pulls read discussion forum message counts from DB List discussionForumReadMessageCounts = messageManager .findDiscussionForumReadMessageCountsForAllSitesByPermissionLevelId(siteListMinusGrouped, roleList); discussionForumReadMessageCounts.addAll(messageManager .findDiscussionForumReadMessageCountsForAllSitesByPermissionLevelName(siteListMinusGrouped, roleList)); // if no read messages, totals are current message counts if (discussionForumReadMessageCounts.isEmpty()) { for (Iterator iter = discussionForumMessageCounts.iterator(); iter.hasNext();) { Object [] count = (Object []) iter.next(); Object [] finalCount = new Object [2]; finalCount[0] = count[0]; finalCount[1] = count[2]; compiledDFMessageCounts.add(finalCount); } } else { // else get correct read count discussionForumReadMessageCounts = filterMessageCountsByRole(discussionForumReadMessageCounts); // subtract read from total to get unread counts compiledDFMessageCounts = computeUnreadDFMessages( discussionForumMessageCounts, discussionForumReadMessageCounts); } // end (discussionForumReadMessageCounts.isEmpty()) - after retrieving read messages from db } // end (! discussionForumMessageCounts.isEmpty()) - after initial retrieval of messages from db } // end (! discussionForumMessageCounts.isEmpty()) compiledDFMessageCounts.addAll(groupedSitesCounts); // add in counts for sites with topics that don't have // membership items associated with them (ie, defaults) addNonMICounts(nonMICounts, compiledDFMessageCounts); } return compiledDFMessageCounts; } /** * Adds in counts for all sites that have topics with no associated * membership items (permissions), ie, uses defaults * @param nonMICounts * @param compiledDFMessageCounts */ private void addNonMICounts(List<Object []> nonMICounts, List<Object []> compiledDFMessageCounts) { Map<String, Object []> compiledCountsMap = new HashMap<String, Object []>(); List<Object []> countsToAdd = new ArrayList<Object []>(); for (Object [] compiledCount: compiledDFMessageCounts) { compiledCountsMap.put((String) compiledCount[0], compiledCount); } for (Object [] nonMIcount: nonMICounts) { Object [] currentUnreadCount = compiledCountsMap.get((String) nonMIcount[0]); if (currentUnreadCount != null) { currentUnreadCount[1] = Integer.valueOf((Integer) currentUnreadCount[1]).intValue() + Integer.valueOf((Integer) nonMIcount[1]).intValue(); } else { countsToAdd.add(nonMIcount); } } compiledDFMessageCounts.addAll(countsToAdd); } /** * Removes all sites user does not want message info about and * returns all sites left * * @param allSites * List of all sites user is a member of * * @return * List of sites user wants notification about */ private List filterOutExcludedSites(List allSites) { final List excludedSites = getExcludedSitesFromTabs(); if (excludedSites != null) { for (Iterator excludeIter = excludedSites.iterator(); excludeIter.hasNext(); ) { final String siteId = (String) excludeIter.next(); allSites.remove(siteId); } } return allSites; } /** * Return List to populate page if in MyWorkspace * * @return * List of DecoratedCompiledMessageStats to populate MyWorkspace page */ private List getMyWorkspaceContents() { if (myWorkspaceContents != null) { return myWorkspaceContents; } else { final List contents = new ArrayList(); Object[] unreadDFCount; Object[] pmCounts; // Used to determine if there are any sites to view on UI sitesToView = false; // retrieve what sites is this user a member of final List siteList = filterOutExcludedSites(getSiteList()); // no sites to work with, set boolean variable and return if (siteList.isEmpty()) { sitesToView = false; myWorkspaceContents = contents; return contents; } // ******* Pulls unread private message counts from DB ******* final List privateMessageCounts = pvtMessageManager .getPrivateMessageCountsForAllSites(); // ******* Pulls unread discussion forum message counts from DB ******* List unreadDFMessageCounts = compileDFMessageCount(siteList); // If both are empty, no unread messages so // create 0 count beans for both types for all sites not filtered so // displays proper messages if (privateMessageCounts.isEmpty() && unreadDFMessageCounts.isEmpty()) { for (Iterator siteIter = siteList.iterator(); siteIter.hasNext(); ) { String siteId = ""; Site site= null; // ************ Get next site from List ************ try { siteId = (String) siteIter.next(); site = getSite(siteId); } catch (IdUnusedException e) { // Wierdness has happened - pulled from SiteService but now can't // find it. Log and skip LOG.error("IdUnusedException attempting to access site " + siteId); continue; } // ************ Each row on page gets info stored in DecoratedCompiledMessageStats bean ************ final DecoratedCompiledMessageStats dcms = new DecoratedCompiledMessageStats(); // fill site title dcms.setSiteName(site.getTitle()); dcms.setSiteId(siteId); dcms.setUnreadForumsAmt(0); dcms.setUnreadPrivateAmt(0); dcms.setMcPageURL(getMCPageURL(siteId)); dcms.setPrivateMessagesUrl(generatePrivateTopicMessagesUrl(siteId)); try { dcms.setMessagesandForums(isMessageForumsPageInSite(getSite(siteId))); dcms.setMessages(isMessagesPageInSite(getSite(siteId))); dcms.setForums(isForumsPageInSite(getSite(siteId))); } catch (IdUnusedException e) { LOG.error("IdUnusedException while trying to determine what tools are in site " + siteId + "to set decorated synoptic messages & forums bean values."); } contents.add(dcms); sitesToView = true; } myWorkspaceContents = contents; return contents; } //============= At least some unread messages so process ============= // ************ loop through info to fill decorated bean ************ for (Iterator si = siteList.iterator(); si.hasNext();) { boolean hasPrivate = false; boolean hasDF = false; String siteId = ""; Site site= null; // ************ Get next site from List ************ try { siteId = (String) si.next(); site = getSite(siteId); } catch (IdUnusedException e) { // Wierdness has happened - pulled from SiteService but now can't // find it. Log and skip LOG.error("IdUnusedException attempting to access site " + siteId); continue; } // Determine if current site has unread private messages final int PMpos = indexOf(siteId, getSiteIds(privateMessageCounts)); if (PMpos != -1) { pmCounts = (Object []) privateMessageCounts.get(PMpos); // to make searching for remaining counts more efficient privateMessageCounts.remove(pmCounts); } else { pmCounts = new Object[1]; pmCounts[0] = ""; } // Determine if current site has unread discussion forum messages final int DFpos = indexOf(siteId, getSiteIds(unreadDFMessageCounts)); if (DFpos != -1) { unreadDFCount = (Object []) unreadDFMessageCounts.get(DFpos); // to make searching for remaining counts more efficient unreadDFMessageCounts.remove(DFpos); } else { unreadDFCount = new Object[1]; unreadDFCount[0] = ""; } // ************ Each row on page gets info stored in DecoratedCompiledMessageStats bean ************ final DecoratedCompiledMessageStats dcms = new DecoratedCompiledMessageStats(); // fill site title dcms.setSiteName(site.getTitle()); dcms.setSiteId(siteId); // Put check here because if not in site, skip if (isMessageForumsPageInSite(site) || isMessagesPageInSite(site)) { // ************ checking for unread private messages for this site ************ if (siteId.equals(pmCounts[0])) { if (isMessagesPageInSite(site)) { dcms.setUnreadPrivateAmt(((Integer) pmCounts[1]).intValue()); hasPrivate = true; } else { // check if not enabled final Area area = areaManager.getAreaByContextIdAndTypeId(siteId, typeManager.getPrivateMessageAreaType()); if (area != null) { if (area.getEnabled().booleanValue()) { dcms.setUnreadPrivateAmt(((Integer) pmCounts[1]).intValue()); hasPrivate = true; } else { dcms.setUnreadPrivateAmt(0); hasPrivate = true; } } else { dcms.setUnreadPrivateAmt(0); hasPrivate = true; } } } else { dcms.setUnreadPrivateAmt(0); hasPrivate = true; } } if (isMessageForumsPageInSite(site) || isForumsPageInSite(site)) { // ************ check for unread discussion forum messages on this site ************ if (siteId.equals(unreadDFCount[0])) { // counts exist, so put it in decorated bean dcms.setUnreadForumsAmt(((Integer) unreadDFCount[1]).intValue()); hasDF = true; } else { // no unread counts, so set to zero dcms.setUnreadForumsAmt(0); hasDF = true; } } // ************ get the page URL for Message Center************ // only if unread messages, ie, only if row will appear on page if (hasPrivate || hasDF) { dcms.setMcPageURL(getMCPageURL(siteId)); dcms.setPrivateMessagesUrl(generatePrivateTopicMessagesUrl(siteId)); try { dcms.setMessagesandForums(isMessageForumsPageInSite(getSite(siteId))); dcms.setMessages(isMessagesPageInSite(getSite(siteId))); dcms.setForums(isForumsPageInSite(getSite(siteId))); } catch (IdUnusedException e) { LOG.error("IdUnusedException while trying to determine what tools are in site " + siteId + "to set decorated synoptic messages & forums bean values."); } contents.add(dcms); sitesToView = true; } } myWorkspaceContents = contents; return contents; } } /** * Returns a list of Strings stored in index 0 of Object [] members of * list passed in. * * @param counts * List of Object [] members whose element at index 0 is a String * * @return * List of Strings extracted from list passed in */ private List getSiteIds(List counts) { final List results = new ArrayList(); if (! counts.isEmpty()) { for (final Iterator iter = counts.iterator(); iter.hasNext(); ) { final Object [] pmCount = (Object []) iter.next(); results.add(pmCount[0]); } } return results; } /** * Returns List to populate page if on Home page of a site * * @return * List of DecoratedCompiledMessageStats for a particular site */ private DecoratedCompiledMessageStats getSiteContents() { if (siteContents == null) { final DecoratedCompiledMessageStats dcms = new DecoratedCompiledMessageStats(); // Check if tool within site // if so, get stats for just this site if (isMessageForumsPageInSite() || isMessagesPageInSite()) { int unreadPrivate = 0; dcms.setSiteName(getSiteName()); dcms.setSiteId(getSiteId()); // Get private message area so we can get the private message forum so we can get the // List of topics so we can get the Received topic to finally determine number of unread messages // only check if Messages & Forums in site, just Messages is on by default boolean isEnabled; final Area area = pvtMessageManager.getPrivateMessageArea(); if (isMessageForumsPageInSite()) { isEnabled = area.getEnabled().booleanValue(); } else { isEnabled = true; } if (isEnabled) { List aggregateList = new ArrayList(); pvtMessageManager.initializePrivateMessageArea(area, aggregateList); unreadPrivate = pvtMessageManager.findUnreadMessageCount( typeManager.getReceivedPrivateMessageType(), aggregateList); dcms.setUnreadPrivateAmt(unreadPrivate); dcms.setPrivateMessagesUrl(generatePrivateTopicMessagesUrl(getSiteId())); } else { dcms.setUnreadPrivateAmt(0); dcms.setPrivateMessagesUrl(getMCPageURL()); } } if (isMessageForumsPageInSite() || isForumsPageInSite()) { // Number of unread forum messages is a little harder // need to loop through all topics and add them up final List topicsList = forumManager.getDiscussionForums(); int unreadForum = 0; final Iterator forumIter = topicsList.iterator(); while (forumIter.hasNext()) { final DiscussionForum df = (DiscussionForum) forumIter.next(); final List topics = df.getTopics(); final Iterator topicIter = topics.iterator(); while (topicIter.hasNext()) { final DiscussionTopic topic = (DiscussionTopic) topicIter.next(); if (uiPermissionsManager.isRead(topic, df)) { if (!topic.getModerated().booleanValue() || (topic.getModerated().booleanValue() && uiPermissionsManager.isModeratePostings(topic, df))) { unreadForum += messageManager.findUnreadMessageCountByTopicId(topic.getId()); } else { // b/c topic is moderated and user does not have mod perm, user may only // see approved msgs or pending/denied msgs authored by user unreadForum += messageManager.findUnreadViewableMessageCountByTopicId(topic.getId()); } } } } dcms.setUnreadForumsAmt(unreadForum); dcms.setMcPageURL(getMCPageURL()); } else { // TODO: what to put on page? Alert? Leave Blank? } siteContents = dcms; } return siteContents; } /** * Determines if any MF tool in site. variable is transient to be per request. * * @return * TRUE if Messages & Forums, Messages, or Forums tool in site */ public boolean isAnyMFToolInSite() { if (anyMFToolInSite == null) { anyMFToolInSite = isMessageForumsPageInSite() || isMessagesPageInSite() || isForumsPageInSite(); } return anyMFToolInSite; } /** * Returns List of decoratedCompiledMessageStats. Called by * jsp page and main processing of list to be displayed. * <p> * Used by both MyWorkspace and site Home page. * * @return * List of decoratedCompiledMessageStats */ public List getContents() { if (isMyWorkspace()) { // Get stats for "all" sites this user is a member of // and has not turned displaying info off return getMyWorkspaceContents(); } else { // refactored to not use dataTable 12/12/06 return new ArrayList(); } } /** * Retrieve the site display title * * @return * String of the title of the site */ private String getSiteName() { try { return getSite(getContext()).getTitle(); } catch (IdUnusedException e) { LOG.error("IdUnusedException when trying to access site " + e.getMessage()); } return null; } /** * Returns the site id String * * @return * The id for current site */ private String getSiteId() { try { return getSite(getContext()).getId(); } catch (IdUnusedException e) { LOG.error("IdUnusedException when trying to access site " + e.getMessage()); } return null; } /** * Returns List of unread messages organized by site * * @param totalMessages * List of all messages by site * * @param readMessages * List of all read messages by site * * @param totalNoAccessMessages * List of all messages user does not have access to by site * * @pararm totalNoAccessReadMessages * List of all read messages user does not have access to by site * (ie, no read permission for that topic) * * @return * List of unread messages by site */ private List computeUnreadDFMessages(List totalMessages, List readMessages) { final List unreadDFMessageCounts = new ArrayList(); final List readSiteIds = getSiteIds(readMessages); // Constructs the unread message counts final Iterator dfMessagesIter = totalMessages.iterator(); while (dfMessagesIter.hasNext()) { final Object [] dfMessageCountForASite = (Object[]) dfMessagesIter.next(); final Object[] siteDFInfo = new Object[2]; siteDFInfo[0] = (String) dfMessageCountForASite[0]; int pos = indexOf((String) siteDFInfo[0], readSiteIds); // read message count for this site found, so subtract it if (pos != -1) { final Object [] dfReadMessageCountForASite = (Object []) readMessages.get(pos); siteDFInfo[1] = Integer.valueOf(((Integer) dfMessageCountForASite[2]).intValue() - ((Integer) dfReadMessageCountForASite[2]).intValue()); // done with it, remove from list readMessages.remove(pos); readSiteIds.remove(pos); } else { // No messages read for this site so message count = unread message count siteDFInfo[1] = (Integer) dfMessageCountForASite[2]; } unreadDFMessageCounts.add(siteDFInfo); } return unreadDFMessageCounts; } /** * @return TRUE if Message Forums (Message Center) exists in this site, * FALSE otherwise */ public boolean isMessageForumsPageInSite() { boolean mfToolExists = false; try { final Site thisSite = getSite(getContext()); mfToolExists = isMessageForumsPageInSite(thisSite); } catch (IdUnusedException e) { LOG.error("IdUnusedException while trying to check if site has MF tool."); } return mfToolExists; } /** * @return TRUE if Messages & Forums (Message Center) exists in this site, * FALSE otherwise */ private boolean isMessageForumsPageInSite(Site thisSite) { if (mfPageInSiteMap == null) { mfPageInSiteMap = new HashMap(); } Boolean isMFPageInSite; if ((isMFPageInSite = (Boolean) mfPageInSiteMap.get(thisSite)) == null) { isMFPageInSite = isToolInSite(thisSite, DiscussionForumService.MESSAGE_CENTER_ID); mfPageInSiteMap.put(thisSite, isMFPageInSite); } return isMFPageInSite; } /** * @return TRUE if Messages tool exists in this site, * FALSE otherwise */ public boolean isForumsPageInSite() { boolean mfToolExists = false; try { final Site thisSite = getSite(getContext()); mfToolExists = isForumsPageInSite(thisSite); } catch (IdUnusedException e) { LOG.error("IdUnusedException while trying to check if site has MF tool."); } return mfToolExists; } /** * @return TRUE if Forums tool exists in this site, * FALSE otherwise */ private boolean isForumsPageInSite(Site thisSite) { return isToolInSite(thisSite, DiscussionForumService.FORUMS_TOOL_ID); } /** * @return TRUE if Messages tool exists in this site, * FALSE otherwise */ public boolean isMessagesPageInSite() { boolean mfToolExists = false; try { final Site thisSite = getSite(getContext()); mfToolExists = isMessagesPageInSite(thisSite); } catch (IdUnusedException e) { LOG.error("IdUnusedException while trying to check if site has MF tool."); } return mfToolExists; } /** * @return TRUE if Messages tool exists in this site, * FALSE otherwise */ private boolean isMessagesPageInSite(Site thisSite) { return isToolInSite(thisSite, DiscussionForumService.MESSAGES_TOOL_ID); } /** * Return TRUE if tool with id passed in exists in site passed in * FALSE otherwise. * * @param thisSite * Site object to check * @param toolId * Tool id to be checked * * @return */ private boolean isToolInSite(Site thisSite, String toolId) { final Collection toolsInSite = thisSite.getTools(toolId); return ! toolsInSite.isEmpty(); } /** * Returns the URL for the page the Message Center tool is on. Called if * tool on home page of a site. * * @return String A URL so the user can click to go to Message Center. * Needed since tool could possibly by in MyWorkspace */ private String getMCPageURL() { return getMCPageURL(getContext()); } /** * Returns the URL using a helper to go to MC home page directly. * * @return String A URL so the user can click to go to Message Center. * Needed since tool could possibly by in MyWorkspace */ private String getMCPageURL(String siteId) { ToolConfiguration mcTool = null; String url = null; try { String toolId = ""; final Site site = getSite(siteId); if (isMessageForumsPageInSite(site)) { toolId = DiscussionForumService.MESSAGE_CENTER_ID; } else if (isForumsPageInSite(site)) { toolId = DiscussionForumService.FORUMS_TOOL_ID; } else if (isMessagesPageInSite(site)) { toolId = DiscussionForumService.MESSAGES_TOOL_ID; } mcTool = site.getToolForCommonId(toolId); if (mcTool != null) { if (toolId == DiscussionForumService.MESSAGE_CENTER_ID) { url = ServerConfigurationService.getPortalUrl() + "/directtool/" + mcTool.getId() + "/sakai.messageforums.helper.helper/main"; } else if (toolId == DiscussionForumService.FORUMS_TOOL_ID) { url = ServerConfigurationService.getPortalUrl() + "/directtool/" + mcTool.getId() + "/sakai.messageforums.helper.helper/discussionForum/forumsOnly/dfForums"; } else if (toolId == DiscussionForumService.MESSAGES_TOOL_ID) { url = ServerConfigurationService.getPortalUrl() + "/directtool/" + mcTool.getId() + "/sakai.messageforums.helper.helper/privateMsg/pvtMsgHpView"; } } } catch (IdUnusedException e) { // Weirdness since site ids used gotten from SiteService LOG.error("IdUnusedException while trying to check if site has MF tool."); } return url; } /** * This marks all Private messages as read for a particular site * * @param ActionEvent e */ public void processReadAll(ActionEvent e) { //need modified to support internationalization final String typeUuid = typeManager.getReceivedPrivateMessageType(); if (isMyWorkspace()) { // if within MyWorkspace, need to find the siteId final FacesContext context = FacesContext.getCurrentInstance(); final Map requestParams = context.getExternalContext() .getRequestParameterMap(); final String contextId = (String) requestParams.get(CONTEXTID); final List privateMessages = pvtMessageManager .getMessagesByTypeByContext(typeUuid, contextId); if (privateMessages == null) { LOG.error("No messages found while attempting to mark all as read " + "from synoptic Message Center tool."); } else { for (Iterator iter = privateMessages.iterator(); iter.hasNext();) { pvtMessageManager.markMessageAsReadForUser( (PrivateMessage) iter.next(), contextId); } } } else { // Get the site id and user id and call query to // mark them all as read List privateMessages = pvtMessageManager.getMessagesByType( typeUuid, PrivateMessageManager.SORT_COLUMN_DATE, PrivateMessageManager.SORT_DESC); if (privateMessages == null) { LOG.error("No messages found while attempting to mark all as read " + "from synoptic Message Center tool."); } else { // TODO: construct query to be one roundtrip to DB for (Iterator iter = privateMessages.iterator(); iter.hasNext();) { pvtMessageManager.markMessageAsReadForUser((PrivateMessage) iter.next()); } } } } /** * Returns the Site object for this id, if it exists. * If not, returns IdUnusedException * * @param siteId * The site id to check * * @return * Site object for this id */ private Site getSite(String siteId) throws IdUnusedException { if (sitesMap == null) { sitesMap = new HashMap(); } if (sitesMap.get(siteId) == null) { Site site = SiteService.getSite(siteId); sitesMap.put(site.getId(), site); return site; } else { return (Site) sitesMap.get(siteId); } } /** * Returns current context * * @return * String The site id (context) where tool currently located */ private String getContext() { return ToolManager.getCurrentPlacement().getContext(); } /** * Returns a list of site ids as well as populating a Map of site objects * * @return * List A List of site ids that is published and the user is a member of */ public List getSiteList() { if (siteList == null) { siteList = new ArrayList(); if (sitesMap == null) { sitesMap = new HashMap(); } List mySites = SiteService.getSites(org.sakaiproject.site.api.SiteService.SelectionType.ACCESS, null,null,null,org.sakaiproject.site.api.SiteService.SortType.TITLE_ASC, null); Iterator lsi = mySites.iterator(); if (!lsi.hasNext()) { LOG.debug("User " + SessionManager.getCurrentSessionUserId() + " does not belong to any sites."); return mySites; } // only display sites that are published and have Message Center in them while (lsi.hasNext()) { Site site = (Site) lsi.next(); // filter out unpublished if (site.isPublished()) { siteList.add(site.getId()); sitesMap.put(site.getId(), site); } } } return siteList; } /** * Returns a map of context id, Received folder uuid pairs */ private void constructReceivedUuidMap(List receivedUuidsForAllSites) { receivedFolderUuidByContextId = new HashMap(); for (Iterator listIter = receivedUuidsForAllSites.iterator(); listIter.hasNext();) { Object [] uuidRow = (Object []) listIter.next(); receivedFolderUuidByContextId.put(uuidRow[0], uuidRow[1]); } } /** * Return Received folder uuid */ private String getUuidFromMap(String contextId) { if (receivedFolderUuidByContextId == null) { List tempSiteList = new ArrayList(); tempSiteList.addAll(filterOutExcludedSites(getSiteList())); List receivedUuidsForAllSites = new ArrayList(); if (tempSiteList.size() > 0) { receivedUuidsForAllSites = forumsManager. getReceivedUuidByContextId(tempSiteList); } constructReceivedUuidMap(receivedUuidsForAllSites); } return (String) receivedFolderUuidByContextId.get(contextId); } /** * Construct the Url to bring up the Private Message section * for the site whose id is passed in * * @param contextId * The site id * * @return * String containing the Url to call the helper to move * to the Private Message section of a site */ public String generatePrivateTopicMessagesUrl(String contextId) { if (pvtTopicMessageUrl != null && pvtTopicMessageUrl.contains(contextId)) { return pvtTopicMessageUrl; } else { Topic receivedTopic = null; String receivedTopicUuid = null; if ((receivedTopicUuid = getUuidFromMap(contextId)) == null) { Area area = areaManager.getAreaByContextIdAndTypeId(contextId, typeManager.getPrivateMessageAreaType()); if (area != null) { if (isMessagesPageInSite() || area.getEnabled().booleanValue() || pvtMessageManager.isInstructor()){ /* TODO: determine if receivedTopicUuid = ""; // is OK? */ PrivateForum pf = pvtMessageManager.initializePrivateMessageArea(area, new ArrayList()); pf = pvtMessageManager.initializationHelper(pf, area); List pvtTopics = pf.getTopics(); Collections.sort(pvtTopics, PrivateTopicImpl.TITLE_COMPARATOR); //changed to date comparator receivedTopic = (Topic) pvtTopics.iterator().next(); receivedTopicUuid = receivedTopic.getUuid(); receivedFolderUuidByContextId.put(contextId, receivedTopicUuid); } } } ToolConfiguration mcTool = null; String url = null; try { String toolId = ""; final Site site = getSite(contextId); if (isMessageForumsPageInSite(site)) { toolId = DiscussionForumService.MESSAGE_CENTER_ID; } else if (isMessagesPageInSite(site)) { toolId = DiscussionForumService.MESSAGES_TOOL_ID; } else if (isForumsPageInSite(site)) { toolId = DiscussionForumService.FORUMS_TOOL_ID; } mcTool = site.getToolForCommonId(toolId); if (mcTool != null) { pvtTopicMessageUrl = ServerConfigurationService.getPortalUrl() + "/directtool/" + mcTool.getId() + "/sakai.messageforums.helper.helper/privateMsg/pvtMsg?pvtMsgTopicId=" + receivedTopicUuid + "&contextId=" + contextId + "&selectedTopic=" + PrivateMessagesTool.PVTMSG_MODE_RECEIVED; return pvtTopicMessageUrl; } } catch (IdUnusedException e) { LOG.error("IdUnusedException attempting to move to Private Messages for a site. Site id used is: " + contextId); } } return ""; } /** * Pulls excluded site ids from Tabs preferences */ private List getExcludedSitesFromTabs() { final Preferences prefs = preferencesService.getPreferences( SessionManager.getCurrentSessionUserId()); final ResourceProperties props = prefs.getProperties(TABS_EXCLUDED_PREFS); final List l = props.getPropertyList(TAB_EXCLUDED_SITES); return l; } /** * Find the object in the list that has this value - return the position. * * @param value * The site id to find. * @param siteList * The list of Site objects. * @return The index position in siteList of the site with site id = value, or -1 if not found. */ protected int indexOf(String value, List siteList) { if (LOG.isDebugEnabled()) { LOG.debug("indexOf(String " + value + ", List " + siteList + ")"); } for (int i = 0; i < siteList.size(); i++) { final String siteId = (String) siteList.get(i); if (siteId.equals(value)) { return i; } } return -1; } /** * Is a user logged in. * @return <code>true</code> if the current request is being made by a logged in user. */ public boolean isLoggedIn() { return SessionManager.getCurrentSessionUserId() != null; } }