/**
* OLAT - Online Learning and Training
* http://www.olat.org
*
* This software is protected by the OLAT software license.
* Use is subject to license terms.
* See LICENSE.TXT in this distribution for details.
*
* Copyright (c) 1999-2006 at Multimedia- & E-Learning Services (MELS),
* University of Zurich, Switzerland.
* All rights reserved.
*
*/
package org.olat.instantMessaging;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterGroup;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.RosterPacket;
import org.olat.util.Util;
/**
* Description:
Instant Messaging Client class based on the open source
* library SMACK (www.jivesoftware.org). It provides connection to the
* IM-server, sends presence packets, listens to messages and subscription
* requests. Methods that mention the velocity rendering stuff are uses by
* template files that render the html-response that for they dont have
* references java source code.
*
* @version Initial Date: 14.10.2004
*
* @author Guido Schnider
*/
public class InstantMessagingClient {
private static Logger log = Util.getLogger(InstantMessagingClient.class);
// username is olat global unique username
private final String username;
// password is auto generated and only used for instant messaging
private final String password;
protected XMPPConnection connection = null;
private long connectionTimestamp = 0;
private Roster roster = null;
// null means 'not connected'
private Presence.Mode presenceMode = null;
private String statusMsg = " ";
// messages are stored if more the one arrives
protected List messages = new ArrayList();
// the jabber id like username@olat.ch
private final String jabberServer;
protected List subscribedUsers = new ArrayList();
private static final String OLATBUDDIES = "OLAT-Buddies";
protected boolean collaborationDisabled = false;
protected boolean isConnected;
private boolean flashClientIsRunning = false;
private boolean showOfflineBuddies = false;
private boolean showGroupsInRoster = true;
private String recentStatusMode = Presence.Mode.AVAILABLE.toString();
/**
* @param username
* @param password
*/
protected InstantMessagingClient(String username, String password) {
this.username = username;
this.password = password;
jabberServer = InstantMessagingModule.getServername();
reconnect(true);
}
/**
* if connections fails upon server crash or other incidents, a new Thread
* gets created and tries to reconnect after a waiting period
*
* @param tryImmediately
*/
protected void reconnect(boolean tryImmediately) {
if (tryImmediately) {
doConnect();
}
}
/**
* connect to the JabberServer
*/
protected void doConnect() {
long initTime = System.currentTimeMillis();
try {
// Debug enables a java window with all traffic between client
// and server.
XMPPConnection.DEBUG_ENABLED = false;
// check if currently used im client is already connected
// (reusing clients from previous session)
connection = new XMPPConnection(jabberServer);
connection.login(username, password);
addMessageListener();
addSubscriptionListener();
addPresenceListener();
isConnected = connection.isConnected();
if (isConnected) {
connectionTimestamp = System.currentTimeMillis();
connection.addConnectionListener(new XMPPConnListener(this));
roster = connection.getRoster();
// subscription accept all = 0
roster.setSubscriptionMode(0);
String defaultStatus = InstantMessagingModule.getDefaultRosterStatus(username);
if (defaultStatus.equals(Presence.Type.UNAVAILABLE.toString())) sendPresence(Presence.Type.UNAVAILABLE, null, 0, null);
else sendPresence(Presence.Type.AVAILABLE, null, 0, Presence.Mode.fromString(defaultStatus));
} else {
log.error("Error while trying to connect to Instant Messaging server (username, server): " + username + ", " + jabberServer
+ " After login connection.isConnected() returned false ", null);
}
} catch (XMPPException e) {
isConnected = false;
log.error("Error while trying to connect to Instant Messaging server (username, server): " + username + ", " + jabberServer
+ " Errormessage: " + e.getMessage());
// reconnect(false);
} catch (Exception e) { // also catch java.lang.IllegalStateException: Not
// connected to server. -> at
// org.jivesoftware.smack.XMPPConnection.addPacketListener(XMPPConnection.java:581)
// and so on
isConnected = false;
log.error("Error while trying to connect to Instant Messaging server (username, server): " + username + ", " + jabberServer
+ " Errormessage: " + e.getMessage());
}
if (log.isDebugEnabled()) log.debug("Connecting to IM server and fetching roster took (milliseconds): "
+ (System.currentTimeMillis() - initTime));
}
/**
* change jabber status. Example: sendPresencePacket(Presence.Type.AVAILABLE,
* "at meeting...", 1, Presence.Mode.AWAY);
*
* @param type
* @param status
* @param priority
* @param mode
*/
protected void sendPresence(Presence.Type type, String status, int priority, Presence.Mode mode) {
// get rif of "&" because they break xml packages!
if (status != null) status = status.replaceAll("&", "&");
if (connection == null || !connection.isConnected()) return;
if (collaborationDisabled) return;
setStatus(mode);
Presence presence = new Presence(type);
if (status == null) {
if (mode == Presence.Mode.AVAILABLE) {
status = InstantMessagingConstants.PRESENCE_MODE_AVAILABLE;
} else if (mode == Presence.Mode.AWAY) {
status = InstantMessagingConstants.PRESENCE_MODE_AWAY;
} else if (mode == Presence.Mode.CHAT) {
status = InstantMessagingConstants.PRESENCE_MODE_CHAT;
} else if (mode == Presence.Mode.DO_NOT_DISTURB) {
status = InstantMessagingConstants.PRESENCE_MODE_DND;
} else if (mode == Presence.Mode.EXTENDED_AWAY) {
status = InstantMessagingConstants.PRESENCE_MODE_XAWAY;
}
presence.setStatus(status);
} else {
presence.setStatus(status);
}
setStatusMsg(presence.getStatus());
presence.setPriority(priority);
if (mode != null) presence.setMode(mode);
try {
connection.sendPacket(presence);
} catch (RuntimeException ex) {
log.error("Error while trying to send Instant Messaging packet. Errormessage: " + ex.getMessage(), ex);
}
}
/**
* while listening to presence packets we are able to determine wether the
* flash client in the second browser window is running or not. Like this we
* get a clear event even when the user closes the window.
*/
private void addPresenceListener() {
PacketFilter filter = new PacketTypeFilter(Presence.class);
connection.createPacketCollector(filter);
PacketListener myListener = new PacketListener() {
public void processPacket(Packet packet) {
Presence presence = (Presence) packet;
// xiff is the resource string that is hardcoded in the xiff
// library.
// if we receive a packet with type "unavailable" and resource
// string
// "xiff" the user closed
// the flash client.
if (presence.getType() == Presence.Type.UNAVAILABLE && presence.getFrom().startsWith(getUsername())
&& presence.getFrom().endsWith("xiff")) setFlashClientIsRunning(false);
if (presence.getType() == Presence.Type.AVAILABLE && presence.getFrom().startsWith(getUsername())
&& presence.getFrom().endsWith("xiff")) {
setFlashClientIsRunning(true);
// TODO:gs presence vom flash client wird nur übermittelt
// beim start und beim stop des
// clients, die presence während dem laufen werden nicht
// übermittelt, Frage??? standardconform???
// setStatus(presence.getMode());
}
}
};
connection.addPacketListener(myListener, filter);
}
/**
* Each client listens to it's own messages
*/
private void addMessageListener() {
PacketFilter filter = new PacketTypeFilter(Message.class);
connection.createPacketCollector(filter);
PacketListener myListener = new PacketListener() {
public void processPacket(Packet packet) {
Message jabbmessage = (Message) packet;
jabbmessage.setProperty("receiveTime", new Long(new Date().getTime()));
if ((jabbmessage.getType() == Message.Type.CHAT || jabbmessage.getType() == Message.Type.NORMAL) && jabbmessage.getBody() != null) {
synchronized (messages) {
messages.add(jabbmessage);
}
}
}
};
connection.addPacketListener(myListener, filter);
}
/**
* By adding this methode (right now added to the contructor) we do have auto
* subscription. All subscribe packets get automatically answered by a
* subscribed packet.
*/
private void addSubscriptionListener() {
PacketFilter filter = new PacketTypeFilter(Presence.class);
connection.createPacketCollector(filter);
PacketListener myListener = new PacketListener() {
public void processPacket(Packet packet) {
Presence presence = (Presence) packet;
if (presence.getType() == Presence.Type.SUBSCRIBE) {
Presence response = new Presence(Presence.Type.SUBSCRIBED);
response.setTo(presence.getFrom());
// System.out.println("subscribed to: "+presence.getFrom());
connection.sendPacket(response);
// ask also for subscription
if (!subscribedUsers.contains(presence.getFrom())) {
response = null;
response = new Presence(Presence.Type.SUBSCRIBE);
response.setTo(presence.getFrom());
connection.sendPacket(response);
// update the roster with the new user
RosterPacket rosterPacket = new RosterPacket();
rosterPacket.setType(IQ.Type.SET);
RosterPacket.Item item = new RosterPacket.Item(presence.getFrom(), parseName(presence.getFrom()));
item.addGroupName(OLATBUDDIES);
item.setItemType(RosterPacket.ItemType.BOTH);
// item.setItemStatus(RosterPacket.ItemStatus.fromString());
rosterPacket.addRosterItem(item);
connection.sendPacket(rosterPacket);
}
}
if (presence.getType() == Presence.Type.SUBSCRIBED) {
subscribedUsers.add(presence.getFrom());
}
}
};
connection.addPacketListener(myListener, filter);
}
/**
* For unsubscription we have to create a packet like:
");
r = Pattern.compile(patternCrNewline);
m = r.matcher(clean);
clean = m.replaceAll("
");
r = Pattern.compile(patternQuote);
m = r.matcher(clean);
clean = m.replaceAll(" ");
r = Pattern.compile(patternDQuote);
m = r.matcher(clean);
clean = m.replaceAll("");
}
return clean;
}
/**
* @return Returns the password.
*/
protected String getPassword() {
return this.password;
}
/**
* @return Returns the username.
*/
protected String getUsername() {
return this.username;
}
/**
* @return Returns true when user is connected to server
*/
protected boolean isConnected() {
return isConnected;
}
protected void setIsConnected(boolean isConnected) {
this.isConnected = isConnected;
}
/**
* @return true if flash client is running otherwise false
*/
protected boolean getFlashClientIsRunning() {
return this.flashClientIsRunning;
}
/**
* When the user opens a second window with the flash client we set this true
*
* @param isRunning Used by Velocity renderer
*/
protected void setFlashClientIsRunning(boolean isRunning) {
this.flashClientIsRunning = isRunning;
}
/**
* TODO:gs used by velocity, change connected client list to client helper
* object
*
* @return Returns the statusMsg.
*/
protected String getStatusMsg() {
return statusMsg;
}
/**
* Free text to add more details to a specific status like status "away" and
* msg "eating lunch until 2pm..."
*
* @param statusMsg The statusMsg to set.
*/
protected void setStatusMsg(String statusMsg) {
if (statusMsg == null) statusMsg = "";
this.statusMsg = statusMsg;
}
/**
* @return Returns the showOfflineBuddies.
*/
protected boolean getShowOfflineBuddies() {
return showOfflineBuddies;
}
/**
* @param showOfflineBuddies The showOfflineBuddies to set.
*/
protected void setShowOfflineBuddies(boolean showOfflineBuddies) {
this.showOfflineBuddies = showOfflineBuddies;
}
/**
* TODO:gs used by velocity, change connected client list to client helper
* object
*
* @return minutes the user is online
*/
protected String getOnlineTime() {
long diff = System.currentTimeMillis() - connectionTimestamp;
return new Integer((int) diff / 1000 / 60).toString();
}
/**
* @return Returns the showGroupsInRoster.
*/
protected boolean isShowGroupsInRoster() {
return showGroupsInRoster;
}
/**
* @param showGroupsInRoster The showGroupsInRoster to set.
*/
protected void setShowGroupsInRoster(boolean showGroupsInRoster) {
this.showGroupsInRoster = showGroupsInRoster;
}
}