/**
* OLAT - Online Learning and Training
* http://www.olat.org
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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.
*
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),
* University of Zurich, Switzerland.
*
*/
package org.olat.instantMessaging;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
import org.jivesoftware.smack.packet.Presence;
import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.BaseSecurityManager;
import org.olat.basesecurity.SecurityGroup;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.commons.taskExecutor.TaskExecutorManager;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.creator.AutoCreator;
import org.olat.core.gui.control.creator.ControllerCreator;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.Roles;
import org.olat.core.logging.LogDelegator;
import org.olat.course.CourseFactory;
import org.olat.course.CourseModule;
import org.olat.course.ICourse;
import org.olat.course.groupsandrights.CourseGroupManager;
import org.olat.group.BusinessGroup;
import org.olat.group.context.BGContextManager;
import org.olat.group.context.BGContextManagerImpl;
import org.olat.instantMessaging.groupchat.GroupChatManagerController;
import org.olat.instantMessaging.rosterandchat.InstantMessagingMainController;
import org.olat.instantMessaging.syncservice.InstantMessagingGroupSynchronisation;
import org.olat.instantMessaging.syncservice.InstantMessagingServerPluginVersion;
import org.olat.instantMessaging.syncservice.InstantMessagingSessionCount;
import org.olat.instantMessaging.syncservice.InstantMessagingSessionItems;
import org.olat.instantMessaging.syncservice.RemoteAccountCreation;
import org.olat.instantMessaging.ui.ConnectedUsersListEntry;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
/**
* Implementation of the InstantMessaging Interface based on the SMACK instant messaging library from jivesoftware.org
*
* Initial Date: 18.01.2005
*
* @author guido
*/
public class SmackInstantMessagingImpl extends LogDelegator implements InstantMessaging {
private IMConfig config;
private InstantMessagingGroupSynchronisation buddyGroupService;
private InstantMessagingSessionCount sessionCountService;
private InstantMessagingSessionItems sessionItemsService;
private RemoteAccountCreation accountService;
ClientManager clientManager;
private IMNameHelper nameHelper;
private AdminUserConnection adminConnecion;
private String clientVersion;
private InstantMessagingServerPluginVersion pluginVersion;
private AutoCreator actionControllerCreator;
private volatile int sessionCount;
private long timeOfLastSessionCount;
/**
* [spring]
*/
private SmackInstantMessagingImpl() {
//
}
/**
* @see org.olat.instantMessaging.InstantMessaging#createClientController(org.olat.core.gui.UserRequest, org.olat.core.gui.control.WindowControl)
*/
@Override
public Controller createClientController(final UserRequest ureq, final WindowControl wControl) {
final InstantMessagingClient client = clientManager.getInstantMessagingClient(ureq.getIdentity().getName());
// there are two versions of the controller, either join the course chat automatically or upon request
client.setGroupChatManager((GroupChatManagerController) actionControllerCreator.createController(ureq, wControl));
return new InstantMessagingMainController(ureq, wControl);
}
/**
* [used by spring]
*/
public void setActionController(final ControllerCreator actionControllerCreator) {
this.actionControllerCreator = (AutoCreator) actionControllerCreator;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#getGroupChatManagerController()
*/
@Override
public GroupChatManagerController getGroupChatManagerController(final UserRequest ureq) {
return clientManager.getInstantMessagingClient(ureq.getIdentity().getName()).getGroupChatManagerController();
}
/**
* @see org.olat.instantMessaging.InstantMessaging#addUserToFriendsRoster(java.lang.String, java.lang.String, java.lang.String, java.lang.String) o_clusterOK by:fj -
* nodes can access the IM server concurrently but only one thread should add a users to a group at the same time. Sync over whole clazz, not time critical as
* accessed by backgrounded threads
*/
// TODO:gs does this need to be synchronized?
@Override
public synchronized boolean addUserToFriendsRoster(String groupOwnerUsername, final String groupId, final String groupname, String addedUsername) {
// we have to make sure the user has an account on the instant messaging
// server
// by calling this it gets created if not yet exists.
addedUsername = nameHelper.getIMUsernameByOlatUsername(addedUsername);
groupOwnerUsername = nameHelper.getIMUsernameByOlatUsername(groupOwnerUsername);
final boolean hasAccount = accountService.hasAccount(addedUsername);
if (!hasAccount) {
clientManager.getInstantMessagingCredentialsForUser(addedUsername);
}
// we do not check whether a group already exists, we create it each time
final List list = new ArrayList();
list.add(groupOwnerUsername);
buddyGroupService.createSharedGroup(groupId, groupname, list);
logDebug("Adding user to roster group::" + groupId + " username: " + addedUsername);
return buddyGroupService.addUserToSharedGroup(groupId, addedUsername);
}
/**
* @see org.olat.instantMessaging.InstantMessaging#removeUserFromFriendsRoster(java.lang.String, java.lang.String)
*/
@Override
public boolean removeUserFromFriendsRoster(final String groupId, final String username) {
final String imUsername = nameHelper.getIMUsernameByOlatUsername(username);
logDebug("Deleting user from roster group::" + groupId + " username: " + imUsername);
return buddyGroupService.removeUserFromSharedGroup(groupId, imUsername);
}
/**
* @see org.olat.instantMessaging.InstantMessaging#deleteRosterGroup(java.lang.String)
*/
@Override
public boolean deleteRosterGroup(final String groupId) {
// groupId is already converted to single/multiple instance version
logDebug("Deleting roster group from instant messaging server::" + groupId);
return buddyGroupService.deleteSharedGroup(groupId);
}
/**
* @param groupId
* @param displayName
*/
@Override
public boolean renameRosterGroup(final String groupId, final String displayName) {
logDebug("Renaming roster group on instant messaging server::" + groupId);
return buddyGroupService.renameSharedGroup(groupId, displayName);
}
/**
* @see org.olat.instantMessaging.InstantMessaging#sendStatus(java.lang.String, java.lang.String)
*/
@Override
public void sendStatus(final String username, final String message) {
// only send status if client is active otherwise course dispose may recreate an connection
if (clientManager.hasActiveInstantMessagingClient(username)) {
final InstantMessagingClient imc = clientManager.getInstantMessagingClient(username);
final String recentStatus = imc.getStatus();
// awareness presence packets get only sended if not "unavailable". Otherwise the unavailable status gets overwritten by an available one.
if (!recentStatus.equals(InstantMessagingConstants.PRESENCE_MODE_UNAVAILABLE)) {
imc.sendPresence(Presence.Type.available, message, 0, Presence.Mode.valueOf(imc.getStatus()));
}
}
}
/**
* @see org.olat.instantMessaging.InstantMessaging#createAccount(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public boolean createAccount(final String username, final String password, final String fullname, final String email) {
boolean success;
success = accountService.createAccount(nameHelper.getIMUsernameByOlatUsername(username), password, fullname, email);
logDebug("Creating new user account on IM server for user:" + username + " returned: " + success);
return success;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#deleteAccount(java.lang.String)
*/
@Override
public boolean deleteAccount(final String username) {
boolean success;
success = accountService.deleteAccount(nameHelper.getIMUsernameByOlatUsername(username));
logDebug("Deleting user account on IM server for user:" + username + " returned: " + success);
return success;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#getIMPassword(java.lang.String)
*/
@Override
public String getIMPassword(final String username) {
return clientManager.getInstantMessagingClient(username).getPassword();
}
/**
* @return Set containing the usernames
*/
@Override
public Set getUsernamesFromConnectedUsers() {
return new HashSet(getClients().keySet());
}
/**
* @see org.olat.instantMessaging.InstantMessaging#getClients()
*/
@Override
public Map getClients() {
return clientManager.getClients();
}
/**
* @see org.olat.instantMessaging.InstantMessaging#enableChat(java.lang.String)
*/
@Override
public void enableChat(final String username) {
clientManager.getInstantMessagingClient(username).enableCollaboration();
logDebug("Enabling chat for user::" + username);
}
/**
* @param username
* @param reason A resason why the chat is disabled like "Doing test"
* @see org.olat.instantMessaging.InstantMessaging#disableChat(java.lang.String, java.lang.String)
*/
@Override
public void disableChat(final String username, final String reason) {
clientManager.getInstantMessagingClient(username).disableCollaboration(reason);
logDebug("Disabling chat for user::" + username + "and reason" + reason);
}
/**
* @see org.olat.instantMessaging.InstantMessaging#countConnectedUsers()
*/
@Override
public int countConnectedUsers() {
final long now = System.currentTimeMillis();
if ((now - timeOfLastSessionCount) > 30000) { // only grab session count every 30s
logDebug("Getting session count from IM server");
try {
TaskExecutorManager.getInstance().runTask(new CountSessionsOnServerTask(sessionCountService, this));
} catch (final RejectedExecutionException e) {
logError("countConnectedUsers: TaskExecutorManager rejected execution of CountSessionsOnServerTask. Cannot update user count", e);
}
timeOfLastSessionCount = System.currentTimeMillis();
}
return sessionCount;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#synchonizeBuddyRoster(org.olat.group.BusinessGroup)
*/
@Override
public boolean synchonizeBuddyRoster(final BusinessGroup group) {
final BaseSecurity securityManager = BaseSecurityManager.getInstance();
final SecurityGroup owners = group.getOwnerGroup();
final SecurityGroup participants = group.getPartipiciantGroup();
final List users = securityManager.getIdentitiesOfSecurityGroup(owners);
users.addAll(securityManager.getIdentitiesOfSecurityGroup(participants));
int counter = 0;
final List usernames = new ArrayList();
for (final Iterator iter = users.iterator(); iter.hasNext();) {
final Identity ident = iter.next();
logDebug("getting im credentials for user::" + ident.getName());
// as jive only adds users to a group that already exist we have to make
// sure they have an account.
clientManager.getInstantMessagingCredentialsForUser(ident.getName());
usernames.add(nameHelper.getIMUsernameByOlatUsername(ident.getName()));
if (counter % 6 == 0) {
DBFactory.getInstance().intermediateCommit();
}
counter++;
}
final String groupId = InstantMessagingModule.getAdapter().createChatRoomString(group);
if (users.size() > 0) { // only sync groups with users
if (!buddyGroupService.createSharedGroup(groupId, group.getName(), usernames)) {
logError("could not create shared group: " + groupId, null);
}
logDebug("synchronizing group::" + group.toString());
} else {
logDebug("empty group: not synchronizing group::" + group.toString());
}
// when looping over all buddygroups and learninggroups close transaction after each group
DBFactory.getInstance().intermediateCommit();
return true;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#synchronizeLearningGroupsWithIMServer()
*/
@Override
public boolean synchronizeLearningGroupsWithIMServer() {
logInfo("Starting synchronisation of LearningGroups with IM server");
final RepositoryManager rm = RepositoryManager.getInstance();
final BGContextManager contextManager = BGContextManagerImpl.getInstance();
// pull as admin
final Roles roles = new Roles(true, true, true, true, false, true, false);
final List allCourses = rm.queryByTypeLimitAccess(CourseModule.getCourseTypeName(), roles);
int counter = 0;
for (final Iterator iterator = allCourses.iterator(); iterator.hasNext();) {
final RepositoryEntry entry = iterator.next();
ICourse course = null;
try {
course = CourseFactory.loadCourse(entry.getOlatResource());
} catch (final Exception e) {
logError("Could not load Course! OlatResourcable: " + entry.getOlatResource(), e);
continue;
}
final CourseGroupManager groupManager = course.getCourseEnvironment().getCourseGroupManager();
final List groups = groupManager.getAllLearningGroupsFromAllContexts();
for (final Iterator iter = groups.iterator(); iter.hasNext();) {
final BusinessGroup group = iter.next();
final boolean syncLearn = InstantMessagingModule.getAdapter().getConfig().isSyncLearningGroups();
final boolean isLearn = group.getType().equals(BusinessGroup.TYPE_LEARNINGROUP);
if (isLearn && !syncLearn) {
final String groupID = InstantMessagingModule.getAdapter().createChatRoomString(group);
if (deleteRosterGroup(groupID)) {
logInfo("deleted unwanted group: " + group.getResourceableTypeName() + " " + groupID, null);
}
counter++;
if (counter % 6 == 0) {
DBFactory.getInstance(false).intermediateCommit();
}
continue;
}
if (!synchonizeBuddyRoster(group)) {
logError("couldn't sync group: " + group.getResourceableTypeName(), null);
}
counter++;
if (counter % 6 == 0) {
DBFactory.getInstance(false).intermediateCommit();
}
}
if (counter % 6 == 0) {
DBFactory.getInstance(false).intermediateCommit();
}
}
logInfo("Ended synchronisation of LearningGroups with IM server: Synched " + counter + " groups");
return true;
}
/**
* Synchronize the groups with the IM system To synchronize buddygroups, use the null-context. Be aware that this action might take some time!
*
* @param groupContext
* @return true if successfull, false if IM server is not running
*/
@Override
public boolean synchronizeAllBuddyGroupsWithIMServer() {
logInfo("Started synchronisation of BuddyGroups with IM server.");
final BGContextManager cm = BGContextManagerImpl.getInstance();
// null as argument pulls all buddygroups
final List groups = cm.getGroupsOfBGContext(null);
int counter = 0;
for (final Iterator iter = groups.iterator(); iter.hasNext();) {
final BusinessGroup group = iter.next();
synchonizeBuddyRoster(group);
counter++;
if (counter % 6 == 0) {
DBFactory.getInstance(false).intermediateCommit();
}
}
logInfo("Ended synchronisation of BuddyGroups with IM server: Synched " + counter + " groups");
return true;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#createChatRoomString(org.olat.core.id.OLATResourceable
*/
@Override
public String createChatRoomString(final OLATResourceable ores) {
final String roomName = ores.getResourceableTypeName() + "-" + ores.getResourceableId();
return nameHelper.getGroupnameForOlatInstance(roomName);
}
@Override
public String createChatRoomJID(final OLATResourceable ores) {
return createChatRoomString(ores) + "@" + config.getConferenceServer();
}
/**
* @see org.olat.instantMessaging.InstantMessaging#getAllConnectedUsers()
*/
@Override
public List getAllConnectedUsers(final Identity currentUser) {
return sessionItemsService.getConnectedUsers(currentUser);
}
/**
* [used by spring]
*
* @param sessionCountService
*/
public void setSessionCountService(final InstantMessagingSessionCount sessionCountService) {
this.sessionCountService = sessionCountService;
}
/**
* [used by spring]
*
* @param sessionCountService
*/
public void setBuddyGroupService(final InstantMessagingGroupSynchronisation buddyGroupService) {
this.buddyGroupService = buddyGroupService;
}
/**
* [used by spring]
*
* @param sessionItemsService
*/
public void setSessionItemsService(final InstantMessagingSessionItems sessionItemsService) {
this.sessionItemsService = sessionItemsService;
}
/**
* [used by spring]
*
* @param accountService
*/
public void setAccountService(final RemoteAccountCreation accountService) {
this.accountService = accountService;
}
/**
* [used by spring]
*
* @param clientManager
*/
public void setClientManager(final ClientManager clientManager) {
this.clientManager = clientManager;
}
/**
* @return client manager where you have access to the IM client itself
*/
@Override
public ClientManager getClientManager() {
return clientManager;
}
@Override
public IMConfig getConfig() {
return config;
}
public void setConfig(final IMConfig config) {
this.config = config;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#hasAccount(java.lang.String)
*/
@Override
public boolean hasAccount(final String username) {
return accountService.hasAccount(nameHelper.getIMUsernameByOlatUsername(username));
}
/**
* @see org.olat.instantMessaging.InstantMessaging#getUserJid(java.lang.String)
*/
@Override
public String getUserJid(final String username) {
return nameHelper.getIMUsernameByOlatUsername(username) + "@" + config.getServername();
}
/**
* @see org.olat.instantMessaging.InstantMessaging#getUsernameFromJid(java.lang.String)
*/
@Override
public String getUsernameFromJid(final String jid) {
return nameHelper.extractOlatUsername(jid);
}
@Override
public String getIMUsername(final String username) {
return nameHelper.getIMUsernameByOlatUsername(username);
}
@Override
public void setNameHelper(final IMNameHelper nameHelper) {
this.nameHelper = nameHelper;
}
/**
* [spring]
*
* @param adminConnection
*/
public void setAdminConnection(final AdminUserConnection adminConnection) {
this.adminConnecion = adminConnection;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#resetAdminConnection()
*/
@Override
public void resetAdminConnection() {
this.adminConnecion.resetAndReconnect();
}
/**
* [spring]
*
* @param clientVersion
*/
public void setClientVersion(final String clientVersion) {
this.clientVersion = clientVersion;
}
public void setServerPluginVersion(final InstantMessagingServerPluginVersion pluginVersion) {
this.pluginVersion = pluginVersion;
}
/**
* @see org.olat.instantMessaging.InstantMessaging#checkServerPlugin()
*/
@Override
public String checkServerPlugin() {
if (clientVersion.equals(pluginVersion.getPluginVersion())) {
return "Jupee! Server plugin and OLAT client run on the same version: " + pluginVersion.getPluginVersion();
} else if (pluginVersion.getPluginVersion() == null) { return "The server does not respond with a version. Do you have the plugin installed? Does the admin user have a running connection to the IM server?"; }
return "OLAT runs on client version: " + clientVersion + " but the server version is: " + pluginVersion.getPluginVersion() + "
Plese upgrade!";
}
@Override
public IMNameHelper getNameHelper() {
return nameHelper;
}
void setSessionCount(final int sessionCount) {
this.sessionCount = sessionCount;
}
}