/**
* Copyright (c) 2008-2012 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.osedu.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.profile2.logic;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Setter;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.sakaiproject.memory.api.Cache;
import org.sakaiproject.profile2.cache.CacheManager;
import org.sakaiproject.profile2.dao.ProfileDao;
import org.sakaiproject.profile2.hbm.model.ProfileFriend;
import org.sakaiproject.profile2.model.BasicConnection;
import org.sakaiproject.profile2.model.Person;
import org.sakaiproject.profile2.types.EmailType;
import org.sakaiproject.profile2.types.PrivacyType;
import org.sakaiproject.profile2.util.ProfileConstants;
import org.sakaiproject.user.api.User;
/**
* Implementation of ProfileConnectionsLogic for Profile2.
*
* @author Steve Swinsburg (s.swinsburg@gmail.com)
*
*/
public class ProfileConnectionsLogicImpl implements ProfileConnectionsLogic {
private static final Logger log = Logger.getLogger(ProfileConnectionsLogicImpl.class);
private Cache cache;
private final String CACHE_NAME = "org.sakaiproject.profile2.cache.connections";
/**
* {@inheritDoc}
*/
public List<BasicConnection> getBasicConnectionsForUser(final String userUuid) {
List<User> users = getConnectedUsers(userUuid);
return getBasicConnections(users);
}
/**
* {@inheritDoc}
*/
public List<Person> getConnectionsForUser(final String userUuid) {
List<User> users = getConnectedUsers(userUuid);
return profileLogic.getPersons(users);
}
/**
* {@inheritDoc}
*/
public int getConnectionsForUserCount(final String userId) {
return getConnectionsForUser(userId).size();
}
/**
* {@inheritDoc}
*/
public List<Person> getConnectionRequestsForUser(final String userId) {
List<User> users = sakaiProxy.getUsers(dao.getRequestedConnectionUserIdsForUser(userId));
return profileLogic.getPersons(users);
}
/**
* {@inheritDoc}
*/
public int getConnectionRequestsForUserCount(final String userId) {
return getConnectionRequestsForUser(userId).size();
}
/**
* {@inheritDoc}
*/
public boolean isUserXFriendOfUserY(String userX, String userY) {
//if same then friends.
//added this check so we don't need to do it everywhere else and can call isFriend for all user pairs.
if(userY.equals(userX)) {
return true;
}
//get friends of current user
//TODO change this to be a single lookup rather than iterating over a list
List<String> friendUuids = getConfirmedConnectionUserIdsForUser(userY);
//if list of confirmed friends contains this user, they are a friend
if(friendUuids.contains(userX)) {
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public List<Person> getConnectionsSubsetForSearch(List<Person> connections, String search) {
return getConnectionsSubsetForSearch( connections, search, false);
}
/**
* {@inheritDoc}
*/
public List<Person> getConnectionsSubsetForSearch(List<Person> connections, String search, boolean forMessaging) {
List<Person> subList = new ArrayList<Person>();
//check auth and get currentUserUuid
String currentUserUuid = sakaiProxy.getCurrentUserId();
if(currentUserUuid == null) {
throw new SecurityException("You must be logged in to get a connection list subset.");
}
for(Person p : connections){
//check for match by name
if(StringUtils.startsWithIgnoreCase(p.getDisplayName(), search)) {
//if reached max size
if(subList.size() == ProfileConstants.MAX_CONNECTIONS_PER_SEARCH) {
break;
}
//if we need to check messaging privacy setting
if(forMessaging){
//if not allowed to be messaged by this user
if(!privacyLogic.isActionAllowed(p.getUuid(), currentUserUuid, PrivacyType.PRIVACY_OPTION_MESSAGES)){
continue;
}
}
//all ok, add them to the list
subList.add(p);
}
}
return subList;
}
/**
* {@inheritDoc}
*/
public int getConnectionStatus(String userA, String userB) {
//current user must be the user making the request
String currentUserId = sakaiProxy.getCurrentUserId();
if(!StringUtils.equals(currentUserId, userA)) {
log.error("User: " + currentUserId + " attempted to get the connection status with " + userB + " on behalf of " + userA);
throw new SecurityException("You are not authorised to perform that action.");
}
ProfileFriend record = dao.getConnectionRecord(userA, userB);
//no connection
if(record == null) {
return ProfileConstants.CONNECTION_NONE;
}
//confirmed
if(record.isConfirmed()) {
return ProfileConstants.CONNECTION_CONFIRMED;
}
//requested
if(StringUtils.equals(userA, record.getUserUuid()) && !record.isConfirmed()) {
return ProfileConstants.CONNECTION_REQUESTED;
}
//incoming
if(StringUtils.equals(userA, record.getFriendUuid()) && !record.isConfirmed()) {
return ProfileConstants.CONNECTION_INCOMING;
}
return ProfileConstants.CONNECTION_NONE;
}
/**
* {@inheritDoc}
*/
public boolean requestFriend(String userId, String friendId) {
if(userId == null || friendId == null){
throw new IllegalArgumentException("Null argument in ProfileLogic.getFriendsForUser");
}
//current user must be the user making the request
String currentUserId = sakaiProxy.getCurrentUserId();
if(!StringUtils.equals(currentUserId, userId)) {
log.error("User: " + currentUserId + " attempted to make connection request to " + friendId + " on behalf of " + userId);
throw new SecurityException("You are not authorised to perform that action.");
}
//make a ProfileFriend object with 'Friend Request' constructor
ProfileFriend profileFriend = new ProfileFriend(userId, friendId, ProfileConstants.RELATIONSHIP_FRIEND);
//make the request
if(dao.addNewConnection(profileFriend)) {
log.info("User: " + userId + " requested friend: " + friendId);
//send email notification
sendConnectionEmailNotification(friendId, userId, EmailType.EMAIL_NOTIFICATION_REQUEST);
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean isFriendRequestPending(String fromUser, String toUser) {
ProfileFriend profileFriend = dao.getPendingConnection(fromUser, toUser);
if(profileFriend == null) {
log.debug("ProfileLogic.isFriendRequestPending: No pending friend request from userId: " + fromUser + " to friendId: " + toUser + " found.");
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
public boolean confirmFriendRequest(final String fromUser, final String toUser) {
if(fromUser == null || toUser == null){
throw new IllegalArgumentException("Null argument in ProfileLogic.confirmFriendRequest");
}
//current user must be the user making the request
String currentUserId = sakaiProxy.getCurrentUserId();
if(!StringUtils.equals(currentUserId, toUser)) {
log.error("User: " + currentUserId + " attempted to confirm connection request from " + fromUser + " on behalf of " + toUser);
throw new SecurityException("You are not authorised to perform that action.");
}
//get pending ProfileFriend object request for the given details
ProfileFriend profileFriend = dao.getPendingConnection(fromUser, toUser);
if(profileFriend == null) {
log.error("ProfileLogic.confirmFriendRequest() failed. No pending friend request from userId: " + fromUser + " to friendId: " + toUser + " found.");
return false;
}
//make necessary changes to the ProfileFriend object.
profileFriend.setConfirmed(true);
profileFriend.setConfirmedDate(new Date());
if(dao.updateConnection(profileFriend)) {
log.info("User: " + fromUser + " confirmed friend request from: " + toUser);
//send email notification
sendConnectionEmailNotification(fromUser, toUser, EmailType.EMAIL_NOTIFICATION_CONFIRM);
//invalidate the confirmed connection caches for each user as they are now stale
evictFromCache(fromUser);
evictFromCache(toUser);
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean ignoreFriendRequest(final String fromUser, final String toUser) {
if(fromUser == null || toUser == null){
throw new IllegalArgumentException("Null argument in ProfileLogic.ignoreFriendRequest");
}
//current user must be the user making the request
String currentUserId = sakaiProxy.getCurrentUserId();
if(!StringUtils.equals(currentUserId, toUser)) {
log.error("User: " + currentUserId + " attempted to ignore connection request from " + fromUser + " on behalf of " + toUser);
throw new SecurityException("You are not authorised to perform that action.");
}
//get pending ProfileFriend object request for the given details
ProfileFriend profileFriend = dao.getPendingConnection(fromUser, toUser);
if(profileFriend == null) {
log.error("ProfileLogic.ignoreFriendRequest() failed. No pending friend request from userId: " + fromUser + " to friendId: " + toUser + " found.");
return false;
}
//delete
if(dao.removeConnection(profileFriend)) {
log.info("User: " + toUser + " ignored friend request from: " + fromUser);
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public boolean removeFriend(String userId, String friendId) {
if(userId == null || friendId == null){
throw new IllegalArgumentException("Null argument in ProfileLogic.removeFriend");
}
//current user must be the user making the request
String currentUserId = sakaiProxy.getCurrentUserId();
if(!StringUtils.equals(currentUserId, userId)) {
log.error("User: " + currentUserId + " attempted to remove connection with " + friendId + " on behalf of " + userId);
throw new SecurityException("You are not authorised to perform that action.");
}
//get the friend object for this connection pair (could be any way around)
ProfileFriend profileFriend = dao.getConnectionRecord(userId, friendId);
if(profileFriend == null){
log.error("ProfileFriend record does not exist for userId: " + userId + ", friendId: " + friendId);
return false;
}
//delete
if(dao.removeConnection(profileFriend)) {
log.info("User: " + userId + " removed friend: " + friendId);
//invalidate the confirmed connection caches for each user as they are now stale
evictFromCache(userId);
evictFromCache(friendId);
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public BasicConnection getBasicConnection(String userUuid) {
return getBasicConnection(sakaiProxy.getUserById(userUuid));
}
/**
* {@inheritDoc}
*/
public BasicConnection getBasicConnection(User user) {
BasicConnection p = new BasicConnection();
p.setUuid(user.getId());
p.setDisplayName(user.getDisplayName());
p.setType(user.getType());
p.setOnlineStatus(getOnlineStatus(user.getId()));
return p;
}
/**
* {@inheritDoc}
*/
public List<BasicConnection> getBasicConnections(List<User> users) {
List<BasicConnection> list = new ArrayList<BasicConnection>();
//get online status
Map<String,Integer> onlineStatus = getOnlineStatus(sakaiProxy.getUuids(users));
//this is created manually so that we can use the bulk retrieval of the online status method.
for(User u:users){
BasicConnection p = new BasicConnection();
p.setUuid(u.getId());
p.setDisplayName(u.getDisplayName());
p.setType(u.getType());
p.setOnlineStatus(onlineStatus.get(u.getId()));
list.add(p);
}
return list;
}
/**
* {@inheritDoc}
*/
public int getOnlineStatus(String userUuid) {
//TODO check prefs and privacy for the user. has the user allowed it?
//check if user has an active session
boolean active = sakaiProxy.isUserActive(userUuid);
if(!active) {
return ProfileConstants.ONLINE_STATUS_OFFLINE;
}
//if active, when was their last event
Long lastEventTime = sakaiProxy.getLastEventTimeForUser(userUuid);
if(lastEventTime == null) {
return ProfileConstants.ONLINE_STATUS_OFFLINE;
}
//if time between now and last event is less than the interval, they are online.
long timeNow = new Date().getTime();
if((timeNow - lastEventTime.longValue()) < ProfileConstants.ONLINE_INACTIVITY_INTERVAL) {
return ProfileConstants.ONLINE_STATUS_ONLINE;
}
//user is online but inactive
return ProfileConstants.ONLINE_STATUS_AWAY;
}
/**
* {@inheritDoc}
*/
public Map<String, Integer> getOnlineStatus(List<String> userUuids) {
//get the list of users that have active sessions
List<String> activeUuids = sakaiProxy.getActiveUsers(userUuids);
//get last event times for the new list
Map<String, Long> lastEventTimes = sakaiProxy.getLastEventTimeForUsers(activeUuids);
long timeNow = new Date().getTime();
//iterate over original list, create the map
Map<String, Integer> map = new HashMap<String, Integer>();
for(String uuid: userUuids) {
if(lastEventTimes.containsKey(uuid)){
//calc time, iff less than interval, online, otherwise away
if((timeNow - lastEventTimes.get(uuid).longValue()) < ProfileConstants.ONLINE_INACTIVITY_INTERVAL) {
map.put(uuid, ProfileConstants.ONLINE_STATUS_ONLINE);
} else {
map.put(uuid, ProfileConstants.ONLINE_STATUS_AWAY);
}
} else {
//no session/no last event time
map.put(uuid, ProfileConstants.ONLINE_STATUS_OFFLINE);
}
}
return map;
}
/**
* Check auth, privacy and get the list of users that are connected to this user.
* @param userUuid
* @return List<User>, will be empty if none or not allowed.
*/
private List<User> getConnectedUsers(final String userUuid) {
//check auth and get currentUserUuid
String currentUserUuid = sakaiProxy.getCurrentUserId();
if(currentUserUuid == null) {
throw new SecurityException("You must be logged in to get a connection list.");
}
List<User> users = new ArrayList<User>();
//check privacy
if(!privacyLogic.isActionAllowed(userUuid, currentUserUuid, PrivacyType.PRIVACY_OPTION_MYFRIENDS)) {
return users;
}
users = sakaiProxy.getUsers(getConfirmedConnectionUserIdsForUser(userUuid));
return users;
}
/**
* Helper method to get the list of confirmed connections for a user as a List<String> of uuids.
*
* <p>First checks the cache and then goes to the dao if necessary.</p>
*
* @param userUuid
* @return List<String> of uuids, empty if none.
*/
private List<String> getConfirmedConnectionUserIdsForUser(final String userUuid) {
List<String> userUuids = new ArrayList<String>();
if(cache.containsKey(userUuid)){
log.debug("Fetching connections from cache for: " + userUuid);
userUuids = (List<String>)cache.get(userUuid);
} else {
userUuids = dao.getConfirmedConnectionUserIdsForUser(userUuid);
if(userUuids != null){
log.debug("Adding connections to cache for: " + userUuid);
cache.put(userUuid, userUuids);
}
}
return userUuids;
}
/**
* Sends an email notification to the users. Used for connections. This formats the data and calls {@link SakaiProxy.sendEmail(String userId, String emailTemplateKey, Map<String,String> replacementValues)}
* @param toUuid user to send the message to - this will be formatted depending on their email preferences for this message type so it is safe to pass any users you need
* @param fromUuid uuid from
* @param messageType the message type to send from ProfileConstants. Retrieves the emailTemplateKey based on this value
*/
private void sendConnectionEmailNotification(String toUuid, final String fromUuid, final EmailType messageType) {
//check if email preference enabled
if(!preferencesLogic.isPreferenceEnabled(toUuid, messageType.toPreference())) {
return;
}
//request
if(messageType == EmailType.EMAIL_NOTIFICATION_REQUEST) {
String emailTemplateKey = ProfileConstants.EMAIL_TEMPLATE_KEY_CONNECTION_REQUEST;
//create the map of replacement values for this email template
Map<String,String> replacementValues = new HashMap<String,String>();
replacementValues.put("senderDisplayName", sakaiProxy.getUserDisplayName(fromUuid));
replacementValues.put("localSakaiName", sakaiProxy.getServiceName());
replacementValues.put("connectionLink", linkLogic.getEntityLinkToProfileConnections());
replacementValues.put("localSakaiUrl", sakaiProxy.getPortalUrl());
replacementValues.put("toolName", sakaiProxy.getCurrentToolTitle());
sakaiProxy.sendEmail(toUuid, emailTemplateKey, replacementValues);
return;
}
//confirm
if(messageType == EmailType.EMAIL_NOTIFICATION_CONFIRM) {
String emailTemplateKey = ProfileConstants.EMAIL_TEMPLATE_KEY_CONNECTION_CONFIRM;
//create the map of replacement values for this email template
Map<String,String> replacementValues = new HashMap<String,String>();
replacementValues.put("senderDisplayName", sakaiProxy.getUserDisplayName(fromUuid));
replacementValues.put("localSakaiName", sakaiProxy.getServiceName());
replacementValues.put("connectionLink", linkLogic.getEntityLinkToProfileHome(fromUuid));
replacementValues.put("localSakaiUrl", sakaiProxy.getPortalUrl());
replacementValues.put("toolName", sakaiProxy.getCurrentToolTitle());
sakaiProxy.sendEmail(toUuid, emailTemplateKey, replacementValues);
return;
}
}
/**
* Helper to evict an item from a cache.
* @param cacheKey the id for the data in the cache
*/
private void evictFromCache(String cacheKey) {
cache.remove(cacheKey);
log.info("Evicted data in cache for key: " + cacheKey);
}
public void init() {
cache = cacheManager.createCache(CACHE_NAME);
}
@Setter
private SakaiProxy sakaiProxy;
@Setter
private ProfileDao dao;
@Setter
private ProfileLogic profileLogic;
@Setter
private ProfileLinkLogic linkLogic;
@Setter
private ProfilePrivacyLogic privacyLogic;
@Setter
private ProfilePreferencesLogic preferencesLogic;
@Setter
private CacheManager cacheManager;
}