/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/presence/trunk/presence-api/api/src/java/org/sakaiproject/presence/api/PresenceService.java $
* $Id: PresenceService.java 7844 2006-04-17 13:06:02Z ggolden@umich.edu $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008, 2009 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.roster;
import java.text.Collator;
import java.text.ParseException;
import java.text.RuleBasedCollator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.api.app.profile.Profile;
import org.sakaiproject.api.app.profile.ProfileManager;
import org.sakaiproject.api.app.roster.Participant;
import org.sakaiproject.api.app.roster.RosterFunctions;
import org.sakaiproject.api.app.roster.RosterManager;
import org.sakaiproject.api.privacy.PrivacyManager;
import org.sakaiproject.authz.api.AuthzGroupService;
import org.sakaiproject.authz.api.FunctionManager;
import org.sakaiproject.authz.api.GroupNotDefinedException;
import org.sakaiproject.authz.api.Member;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.coursemanagement.api.CourseManagementService;
import org.sakaiproject.coursemanagement.api.Section;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.section.api.SectionAwareness;
import org.sakaiproject.section.api.coursemanagement.CourseSection;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.UserNotDefinedException;
public abstract class RosterManagerImpl implements RosterManager {
private static final Log log = LogFactory.getLog(RosterManagerImpl.class);
public abstract ProfileManager profileManager();
public abstract PrivacyManager privacyManager();
public abstract SectionAwareness sectionService();
public abstract SiteService siteService();
public abstract ToolManager toolManager();
public abstract FunctionManager functionManager();
public abstract UserDirectoryService userDirectoryService();
public abstract AuthzGroupService authzGroupService();
public abstract SecurityService securityService();
public abstract CourseManagementService cmService();
public abstract EventTrackingService eventTrackingService();
public void init() {
log.info("init()");
Collection<String> registered = functionManager().getRegisteredFunctions(RosterFunctions.ROSTER_FUNCTION_PREFIX);
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_EXPORT)) {
functionManager().registerFunction(RosterFunctions.ROSTER_FUNCTION_EXPORT);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWALL)) {
functionManager().registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWALL);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN)) {
functionManager().registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO)) {
functionManager().registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWGROUP)) {
functionManager().registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWGROUP);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS)) {
functionManager().registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE)) {
functionManager().registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE);
}
}
public void destroy() {
log.debug("destroy()");
}
private Participant createParticipantByUser(User user, Profile profile) {
Set<String> userIds = new HashSet<String>();
userIds.add(user.getId());
String roleTitle = getUserRoleTitle(user);
return new ParticipantImpl(user, profile, roleTitle, null);
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.api.app.roster.RosterManager#getParticipantById(java.lang.String)
*/
public Participant getParticipantById(String participantId) {
if (log.isDebugEnabled()) {
log.debug("getParticipantById(String" + participantId + ")");
}
if (participantId != null) {
try {
User user = userDirectoryService().getUser(participantId);
Profile profile = profileManager().getUserProfileById(
participantId);
return createParticipantByUser(user, profile);
} catch (UserNotDefinedException e) {
log.error("getParticipantById: " + e.getMessage(), e);
}
}
return null;
}
/**
* Retrieve a complete list of site participants that are viewable by the
* current user.
*
* We have three different view scenarios:
*
* <ol>
*
* <li> View all: These users can see every site member, regardless of
* privacy settings. </li>
*
* <li> View sections: These users can see every member of sections or
* groups for which this user is a TA, regardless of privacy settings. These
* users also see the other site members who have chosen to show themselves
* (privacy setting = off). </li>
*
* <li> View non-hidden participants: These users see only site members who
* have chosen to show themselves (privacy setting = off). </li>
*
* </ol>
*
* @return List
*/
public List<Participant> getRoster() {
List<Participant> participants;
User currentUser = userDirectoryService().getCurrentUser();
boolean viewAllInSite = userHasSitePermission(currentUser,
RosterFunctions.ROSTER_FUNCTION_VIEWALL);
Map<Group, Set<String>> groupMembers = getGroupMembers();
// Users with "viewall" see everybody
if (viewAllInSite) {
participants = getParticipantsInSite();
} else {
participants = getParticipantsInGroups(currentUser, groupMembers);
}
filterHiddenUsers(currentUser, participants, groupMembers);
return participants;
}
public List<Participant> getRoster(String groupReference) {
User currentUser = userDirectoryService().getCurrentUser();
Map<Group, Set<String>> groupMembers = getGroupMembers(groupReference);
List<Participant> participants = getParticipantsInGroups(currentUser, groupMembers);
filterHiddenUsers(currentUser, participants, groupMembers);
return participants;
}
/**
* Gets a Map of the groups in this site (key) to the user IDs for the members in the group (value)
* @return
*/
private Map<Group, Set<String>> getGroupMembers() {
Map<Group, Set<String>> groupMembers = new HashMap<Group, Set<String>>();
Site site;
try {
site = siteService().getSite(getSiteId());
} catch (IdUnusedException ide) {
log.warn(ide);
return groupMembers;
}
Collection<Group> groups = site.getGroups();
//for(Iterator<Group> groupIter = groups.iterator(); groupIter.hasNext();) {
for (Group group : groups) {
//Group group = groupIter.next();
Set<String> userIds = new HashSet<String>();
Set<Member> members = group.getMembers();
for(Iterator<Member> memberIter = members.iterator(); memberIter.hasNext();) {
Member member = memberIter.next();
userIds.add(member.getUserId());
}
groupMembers.put(group, userIds);
}
return groupMembers;
}
/**
* Gets a Map of a group to the user IDs for the members in the group
* @return
*/
private Map<Group, Set<String>> getGroupMembers(String groupReference) {
Map<Group, Set<String>> groupMembers = new HashMap<Group, Set<String>>();
Group group = siteService().findGroup(groupReference);
if(group == null) {
log.warn("Group " + groupReference + " not found");
return groupMembers;
}
//
Set<String> userIds = new HashSet<String>();
Set<Member> members = group.getMembers();
for(Iterator<Member> memberIter = members.iterator(); memberIter.hasNext();) {
Member member = memberIter.next();
userIds.add(member.getUserId());
}
groupMembers.put(group, userIds);
return groupMembers;
}
private void filterHiddenUsers(User currentUser, List<Participant> participants, Map<Group, Set<String>> groupMembers) {
// If the user has view hidden in the site, don't filter anyone out
if(userHasSitePermission(currentUser, RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN)) {
return;
}
// Keep track of the users for which the current user has the group-scoped view hidden permission
Set<String> visibleMembersForCurrentUser = new HashSet<String>();
for (Entry<Group, Set<String>> e : groupMembers.entrySet()) {
if(userHasGroupPermission(currentUser, RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN, e.getKey().getReference())) {
visibleMembersForCurrentUser.addAll(e.getValue());
}
}
// Iterate through the participants, removing the hidden ones that are not in visibleMembersForCurrentUser
Set<String> userIds = new HashSet<String>();
for(Iterator<Participant> iter = participants.iterator(); iter.hasNext();) {
Participant participant = iter.next();
userIds.add(participant.getUser().getId());
}
Set<String> hiddenUsers = privacyManager().findHidden("/site/" + getSiteId(), userIds);
for(Iterator<Participant> iter = participants.iterator(); iter.hasNext();) {
Participant participant = iter.next();
String userId = participant.getUser().getId();
if(hiddenUsers.contains(userId) && ! visibleMembersForCurrentUser.contains(userId)) {
iter.remove();
}
}
}
private List<Participant> getParticipantsInSite() {
Map<String, UserRole> userMap = getUserRoleMap(getSiteReference());
Map<String, Profile> profiles = profileManager().getProfiles(userMap.keySet());
return buildParticipantList(userMap, profiles);
}
private List<Participant> getParticipantsInGroups(User currentUser, Map<Group, Set<String>> groupMembers) {
boolean userHasSiteViewAll = userHasSitePermission(currentUser, RosterFunctions.ROSTER_FUNCTION_VIEWALL);
Set<String> viewableUsers = new HashSet<String>();
//for(Iterator<Group> iter = groupMembers.keySet().iterator(); iter.hasNext();) {
//Group group = iter.next();
for(Entry<Group,Set<String>> e : groupMembers.entrySet()) {
if(userHasGroupPermission(currentUser, RosterFunctions.ROSTER_FUNCTION_VIEWALL, e.getKey().getReference())
|| userHasSiteViewAll) {
viewableUsers.addAll(e.getValue());
}
}
// Build the list of participants
// Use the site reference because we need to display the site role, not the group role
Map<String, UserRole> userMap = getUserRoleMap(getSiteReference());
Map<String, Profile> profilesMap = profileManager().getProfiles(viewableUsers);
return buildParticipantList(userMap, profilesMap);
}
private List<Participant> buildParticipantList(Map<String, UserRole> userMap, Map<String, Profile> profilesMap) {
List<Participant> participants = new ArrayList<Participant>();
Site site = null;
try {
site = siteService().getSite(getSiteId());
} catch (IdUnusedException e) {
log.error("getGroupsWithMember: " + e.getMessage(), e);
return participants;
}
Collection<Group> groups = site.getGroups();
for (Iterator<Entry<String, Profile>> iter = profilesMap.entrySet().iterator(); iter.hasNext();) {
Entry<String, Profile> entry = iter.next();
String userId = entry.getKey();
Profile profile = entry.getValue();
UserRole userRole = userMap.get(userId);
// Profiles may exist for users that have been removed. If there's a profile
// for a missing user, skip the profile. See SAK-10936
if(userRole == null || userRole.user == null) {
log.warn("A profile exists for non-existent user " + userId);
continue;
}
String groupsString = "";
StringBuffer sb = new StringBuffer();
for (Group group : groups)
{
Member member = group.getMember(userId);
if (member !=null)
{
sb.append(group.getTitle() + ", ");
}
}
if (sb.length() > 0)
{
int endIndex = sb.lastIndexOf(", ");
if(endIndex > 0)
{
groupsString = sb.substring(0, endIndex);
} else {
groupsString = sb.toString();
}
}
participants.add(new ParticipantImpl(userRole.user, profile, userRole.role, groupsString));
}
return participants;
}
private Comparator<Group> sortGroups() {
Comparator<Group> groupComparator = new Comparator<Group>() {
Collator r_collator;
{
r_collator = Collator.getInstance();
try
{
r_collator = new RuleBasedCollator(((RuleBasedCollator)Collator.getInstance()).getRules().replaceAll("<'\u005f'", "<' '<'\u005f'"));
}
catch(ParseException e)
{
log.warn(this + " Cannot init RuleBasedCollator. Will use the default Collator instead.", e);
}
}
public int compare(Group one, Group another)
{
return r_collator.compare(one.getTitle(),another.getTitle());
}
};
return groupComparator;
}
static class UserRole {
User user;
String role;
UserRole(User user, String role)
{
this.user = user;
this.role = role;
}
}
/**
* Gets a map of user IDs to UserRole (User + Role) objects.
*
* @return
*/
private Map<String, UserRole> getUserRoleMap(String authzRef) {
Map<String, UserRole> userMap = new HashMap<String, UserRole>();
Set<String> userIds = new HashSet<String>();
Set<Member> members;
// Get the member set
try {
members = authzGroupService().getAuthzGroup(authzRef).getMembers();
} catch (GroupNotDefinedException e) {
log.error("getUsersInAllSections: " + e.getMessage(), e);
return userMap;
}
// Build a map of userId to role
Map<String, String> roleMap = new HashMap<String, String>();
for(Iterator<Member> iter = members.iterator(); iter.hasNext();)
{
Member member = iter.next();
if (member.isActive()) {
// SAK-17286 Only list users that are 'active' not 'inactive'
userIds.add(member.getUserId());
roleMap.put(member.getUserId(), member.getRole().getId());
}
}
// Get the user objects
List<User> users = userDirectoryService().getUsers(userIds);
for (Iterator<User> iter = users.iterator(); iter.hasNext();)
{
User user = iter.next();
String role = roleMap.get(user.getId());
userMap.put(user.getId(), new UserRole(user, role));
}
return userMap;
}
/*
* (non-Javadoc)
*
* @see org.sakaiproject.api.app.roster.RosterManager#currentUserHasExportPerm()
*/
public boolean currentUserHasExportPerm() {
return userHasSitePermission(userDirectoryService().getCurrentUser(),
RosterFunctions.ROSTER_FUNCTION_EXPORT);
}
/**
* Check if given user has the given permission
*
* @param user
* @param permissionName
* @return boolean
*/
private boolean userHasSitePermission(User user, String permissionName) {
if (user == null || permissionName == null) {
if(log.isDebugEnabled()) log.debug("userHasSitePermission passed a null");
return false;
}
String siteReference = getSiteReference();
boolean result = securityService().unlock(user, permissionName, siteReference);
if(log.isDebugEnabled()) log.debug("user " + user.getEid() + ", permission " + permissionName + ", site " + siteReference + " has permission? " + result);
return result;
}
private boolean userHasGroupPermission(User user, String permissionName, String groupReference) {
if (user == null || permissionName == null || groupReference == null) {
if(log.isDebugEnabled()) log.debug("userHasGroupPermission passed a null");
return false;
}
boolean result = authzGroupService().isAllowed(user.getId(), permissionName, groupReference);
if(log.isDebugEnabled()) log.debug("user " + user.getEid() + ", permission " + permissionName + ", group " + groupReference + " has permission? " + result);
return result;
}
/**
* @return siteId
*/
private String getSiteReference() {
return siteService().siteReference(getSiteId());
}
private String getSiteId() {
return toolManager().getCurrentPlacement().getContext();
}
/**
*
* @param user
* @return
*/
private String getUserRoleTitle(User user) {
return authzGroupService().getUserRole(user.getId(),getSiteId());
}
/**
* Determine if sectioning exists in this site
*
* @return
*/
public boolean siteHasSections() {
return ! sectionService().getSections(getSiteId()).isEmpty();
}
public List<CourseSection> getViewableSectionsForCurrentUser() {
User user = userDirectoryService().getCurrentUser();
List<CourseSection> sections = sectionService().getSections(getSiteId());
// If the user can view all groups in the site, return them all
if(userHasSitePermission(user, RosterFunctions.ROSTER_FUNCTION_VIEWGROUP)) {
return sections;
}
for(Iterator<CourseSection> iter = sections.iterator(); iter.hasNext();) {
CourseSection section = iter.next();
if( ! userHasGroupPermission(user, RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, section.getUuid())) {
iter.remove();
}
}
return sections;
}
public List<CourseSection> getViewableEnrollmentStatusSectionsForCurrentUser() {
User user = userDirectoryService().getCurrentUser();
List<CourseSection> sections = getViewableSectionsForCurrentUser();
// If the user can view enrollment statuses at the site level, return all of the sections
boolean siteScopedEnrollmentPermission = userHasSitePermission(user, RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS);
for(Iterator<CourseSection> iter = sections.iterator(); iter.hasNext();) {
CourseSection section = iter.next();
if(section.getEid() != null) {
// This is an official section. Does it have an enrollment set?
Section cmSection = cmService().getSection(section.getEid());
if(cmSection.getEnrollmentSet() == null) {
iter.remove();
} else {
// Does the current user have access to view enrollments for this section?
if( ! siteScopedEnrollmentPermission && ! userHasGroupPermission(user, RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS, section.getUuid())) {
iter.remove();
}
}
}
}
return sections;
}
public boolean isProfilesViewable() {
return userHasSitePermission(userDirectoryService().getCurrentUser(),
RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE);
}
public boolean isOfficialPhotosViewable() {
return userHasSitePermission(userDirectoryService().getCurrentUser(),
RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO);
}
public boolean isGroupMembershipViewable() {
Site site;
try {
site = siteService().getSite(getSiteId());
} catch (IdUnusedException ide) {
log.warn("isGroupMembershipViewable: " + ide);
return false;
}
Collection groups = site.getGroups();
if (groups.isEmpty())
return false;
return true;
}
}