package org.olat.instantMessaging;
import java.util.ArrayList;
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;
/**
*
*
* 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) 2003 OLAT
* Zentrum, University of Zurich, Switzerland. All rights reserved.
*
*
*
* 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 $Revision: 1.39 $ Initial Date: 14.10.2004 Last modified: $Date:
* 2005/02/24 09:39:45 $
* @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 Thread reconnector = null;
/**
* @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();
}
// if not successful, try again every other minute
/*
* if (!isConnected) { synchronized(this) { if (reconnector == null) {
* reconnector = new Thread(new Runnable() { public void run() { boolean
* loop = true; while (loop) { try { //wait 5 minutes and try to reconnect
* if (!Thread.interrupted()) { Thread.sleep(60000*5); if(!isConnected)
* doConnect(); } else { loop = false; } } catch (InterruptedException e1) {
* loop = false; } } }},"Reconnector-"+username); reconnector.start(); } } }
*/
}
/**
* connect to the JabberServer
*/
protected void doConnect() {
try {
//Debug enables a java window with all traffic between client
//and server.
XMPPConnection.DEBUG_ENABLED = false;
connection = new XMPPConnection(jabberServer);
connection.login(username, password);
isConnected = connection.isConnected();
if (isConnected) {
connectionTimestamp = System.currentTimeMillis();
connection.addConnectionListener(new XMPPConnListener(this));
roster = connection.getRoster();
//subscription accept all = 0
roster.setSubscriptionMode(0);
setStatus(Presence.Mode.AVAILABLE);
addMessageListener();
addSubscriptionListener();
addPresenceListener();
}
} catch (XMPPException e) {
isConnected = false;
log.error("Error while trying to connect to Instant Messaging server: " + jabberServer + " Errormessage: " + e.getMessage());
//reconnect(false);
}
}
/**
* 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) {
setStatus(mode);
Presence presence = new Presence(type);
if (status == null) {
if (mode == Presence.Mode.AVAILABLE) {
status = "online";
} else if (mode == Presence.Mode.AWAY) {
status = "away";
} else if (mode == Presence.Mode.CHAT) {
status = "like to chat";
} else if (mode == Presence.Mode.DO_NOT_DISTURB) {
status = "do not disturb";
} else if (mode == Presence.Mode.EXTENDED_AWAY) {
status = "extended away";
}
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 e) {
log.error("Error while trying to send Instant Messaging packet. Errormessage: " + e.getMessage());
}
}
/**
* 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;
if (jabbmessage.getType() == Message.Type.CHAT || jabbmessage.getType() == Message.Type.NORMAL) 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:
*
* @param uname a valid username
*/
protected void removeSubscription(String uname) {
RosterPacket rosterPacket = new RosterPacket();
rosterPacket.setType(IQ.Type.SET);
RosterPacket.Item item = new RosterPacket.Item(uname + "@" + jabberServer, uname);
item.setItemType(RosterPacket.ItemType.REMOVE);
rosterPacket.addRosterItem(item);
try {
connection.sendPacket(rosterPacket);
} catch (RuntimeException e) {
log.error("Error while trying to send Instant Messaging packet. Errormessage: " + e.getMessage());
}
}
/**
* @return message
*/
protected Message getMessage() {
if (!collaborationDisabled) {
synchronized (messages) {
if (this.messages.size() > 0) {
return (Message) this.messages.get(0);
} else {
return null;
}
}
} else {
return null;
}
}
protected List getMessagesByUsername(String usernameFromMsg){
List messagesByUsername = new ArrayList();
for (Iterator i = messages.iterator(); i.hasNext();) {
Message message = (Message)i.next();
if(message.getFrom().equals(usernameFromMsg))
messagesByUsername.add(message);
}
return messagesByUsername;
}
/**
* reset the message object users do this after reading a message in olat
*/
protected void resetMessage() {
if (this.messages.size() > 0) this.messages.remove(0);
}
protected void resetMessagesByUsername(String usernameFromMsg) {
List newMessages = new ArrayList();
for (Iterator i = messages.iterator(); i.hasNext();) {
Message message = (Message)i.next();
if(!message.getFrom().equals(usernameFromMsg))
newMessages.add(message);
}
messages = newMessages;
}
/**
* @return Returns the jid.
*/
public String getJid() {
String jid = this.username + "@" + jabberServer;
return jid;
}
/**
* @return Returns the status.
*/
public String getStatus() {
if (presenceMode != null) {
return presenceMode.toString();
} else {
return Presence.Type.UNAVAILABLE.toString();
}
}
/**
* @param status The status to set.
*/
protected void setStatus(Presence.Mode status) {
this.presenceMode = status;
}
/**
* Used for communication with the flash jabber client.
*
* @param chatpartner
* @param firstMessage
*/
protected void sendChatParamsToFlashClient(String chatpartner, String firstMessage) {
StringBuffer out = new StringBuffer();
out.append("chatpartner:");
out.append(chatpartner);
out.append(",");
out.append("message:" + firstMessage);
sendCustomMessagePacket(out.toString());
}
/**
* Used for communication with the flash jabber client.
*
* @param roomName
*/
protected void sendConferenceParamsToFlashClient(String roomName) {
//String roomname = "chatroom:"+roomName;
//sendCustomMessagePacket(roomname);
StringBuffer out = new StringBuffer();
out.append("chatroom:");
out.append(roomName);
out.append(",");
out.append("commonRoomName:");
out.append(ConferenceRoomManager.getInstance().getRoomName(roomName));
sendCustomMessagePacket(out.toString());
}
/**
* Sends a message type="headline" to the flash client
*
* @param msgBody of the custom message
*/
protected void sendCustomMessagePacket(String msgBody) {
//Chat chat = connection.createChat(olatUsername+"@"+JABBERHOST+"/xiff");
//Message customMsg = chat.createMessage();
Message customMsg = new Message();
//custom messages are sent only to our flash client with ressource /xiff
customMsg.setTo(username + "@" + jabberServer + "/xiff");
customMsg.setType(Message.Type.HEADLINE);
customMsg.setBody(msgBody);
try {
connection.sendPacket(customMsg);
} catch (RuntimeException e) {
log.error("Error while trying to send Instant Messaging packet. Errormessage: " + e.getMessage());
}
}
/**
* @return Returns the roster.
*/
protected Roster getRoster() {
return roster;
}
/**
* Close the connection to the server
*/
protected void closeConnection() {
try {
connection.close();
} catch (RuntimeException e) {
log.error("Error while trying to send Instant Messaging packet. Errormessage: " + e.getMessage());
}
isConnected = false;
if (reconnector != null) {
reconnector.interrupt();
}
}
/**
* Ask an other online user to subscribe to their roster
*
* @param uname
*/
protected void subscribeToUser(String uname) {
Presence presence = new Presence(Presence.Type.SUBSCRIBE);
presence.setTo(uname + "@" + jabberServer);
try {
connection.sendPacket(presence);
} catch (RuntimeException e) {
log.error("Error while trying to send Instant Messaging packet. Errormessage: " + e.getMessage());
}
}
/**
* Sends a subscription request to the username answers are handled by the
* method
*
* @see org.olat.instantMessaging.InstantMessagingClient#addSubscriptionListener()
* @param uname
* @param groupname
*/
protected void subscribeToUser(String uname, String groupname) {
Presence presence = new Presence(Presence.Type.SUBSCRIBE);
presence.setTo(uname + "@" + jabberServer);
try {
connection.sendPacket(presence);
RosterPacket rosterPacket = new RosterPacket();
rosterPacket.setType(IQ.Type.SET);
RosterPacket.Item item = new RosterPacket.Item(uname + "@" + jabberServer, uname);
item.addGroupName(groupname);
item.setItemType(RosterPacket.ItemType.BOTH);
rosterPacket.addRosterItem(item);
connection.sendPacket(rosterPacket);
} catch (RuntimeException e) {
log.error("Error while trying to send Instant Messaging packet. Errormessage: " + e.getMessage());
}
}
/**
* @param xmppAddress jabber jid like guido@swissjabber.org
* @return returns just the name "guido" without the rest
*/
protected String parseName(String xmppAddress) {
if (xmppAddress == null) { return null; }
int atIndex = xmppAddress.indexOf("@");
if (atIndex <= 0) {
return "";
} else {
return xmppAddress.substring(0, atIndex);
}
}
/**
* @param xmppAddressWithRessource like guido@swissjabber.org/office
* @return treurns the jid without the ressource like guido@swissjabber.org
*/
protected String parseJid(String xmppAddressWithRessource) {
if (xmppAddressWithRessource == null) { return null; }
int atIndex = xmppAddressWithRessource.indexOf("/");
if (atIndex <= 0) {
//if no "/" is found we pass back the full adress
return xmppAddressWithRessource;
} else {
return xmppAddressWithRessource.substring(0, atIndex);
}
}
/**
* When doing a test, collaboration by IM should be disabled
*
* @param reason The reason why this user is not allowd to chat e.g. doing
* test By setting the third param to 10 all messages should be send
* to this client even if other clients are up. So we have full
* control over them.
*/
protected void disableCollaboration(String reason) {
collaborationDisabled = true;
sendPresence(Presence.Type.AVAILABLE, reason, 10, Presence.Mode.DO_NOT_DISTURB);
}
/**
* enable collaboration
*/
protected void enableCollaboration() {
collaborationDisabled = false;
setToLowPriority();
}
/**
* Used by Velocity renderer indicates wheater the user has a new Message or
* not
*
* @return true if user has a message
*/
protected boolean hasMessage() {
if (getMessage() != null) {
return true;
} else return false;
}
/**
*
* @return true if user has more messages in the queue
*/
protected boolean hasMessages(){
if (messages.size() > 1) {
return true;
} else return false;
}
/**
*
* @return true if user has received more then one message from the same user
*/
protected boolean hasSeveralMessagesOfSameUser(String usernameFromMsg){
if (messages.size() > 0) {
Message message;
List fromAdresses = new ArrayList();
for (Iterator i = messages.iterator(); i.hasNext();) {
message = (Message)i.next();
fromAdresses.add(message.getFrom());
}
if(fromAdresses.indexOf(usernameFromMsg) != fromAdresses.lastIndexOf(usernameFromMsg)){
return true;
}else return false;
}else return false;
}
/**
* Used by Velocity renderer
*
* @return a String representing the online buddies out of the number of total
* buddies
*/
protected String buddyCountOnline() {
int onlineBuddyEntries = connection.getRoster().getEntryCount();
int allBuddies = onlineBuddyEntries;
for (Iterator l = connection.getRoster().getEntries(); l.hasNext();) {
RosterEntry entry = (RosterEntry) l.next();
Presence presence = connection.getRoster().getPresence(entry.getUser());
if (presence == null) onlineBuddyEntries--;
}
// final string looks like e.g. "(3/5)"
StringBuffer sb = new StringBuffer();
sb.append("(");
sb.append(onlineBuddyEntries);
sb.append("/");
sb.append(allBuddies);
sb.append(")");
return sb.toString();
}
/**
* Used by Velocity renderer
*
* @param groupname
* @return a String representing the online buddies out of the number of total
* buddies for a single group like (3/5)
*/
protected String buddyCountOnlineForGroup(String groupname) {
RosterGroup rosterGroup = connection.getRoster().getGroup(groupname);
int buddyEntries = rosterGroup.getEntryCount();
int allBuddies = buddyEntries;
for (Iterator I = rosterGroup.getEntries(); I.hasNext();) {
RosterEntry entry = (RosterEntry) I.next();
Presence presence = connection.getRoster().getPresence(entry.getUser());
if (presence == null) buddyEntries--;
}
//final string looks like e.g. "(3/5)"
StringBuffer sb = new StringBuffer();
sb.append("(");
sb.append(buddyEntries);
sb.append("/");
sb.append(allBuddies);
sb.append(")");
return sb.toString();
}
/**
* Used by Velocity renderer
*
* @param jid
* @return get a presence for a specific user
*/
protected String getUserPresence(String jid) {
Presence presence = connection.getRoster().getPresence(jid);
String imageName = "offline";
if (presence != null) imageName = presence.getMode().toString();
return imageName;
}
/**
* Used by Velocity renderer Inside the layers we can only use html
* linebreaks.
*
* @return the message body without newline and quotes
*/
protected String getMessageFormatted(String msgBody) {
//Message msg = (Message) this.messages.get(0);
//String messageFormatted = msg.getBody();
String patternNewline = "\n";
String patternCrNewline = "\r\n";
String patternQuote = "'";
String patternDQuote = "\"";
String clean = null;
Pattern r = Pattern.compile(patternNewline);
Matcher m = r.matcher(msgBody);
clean = m.replaceAll("
");
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 runnung 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.
*/
public 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) {
this.statusMsg = statusMsg;
}
/**
* To make shure that we get the subscription packets to this client instead
* of the flash client. Used for disabling collabration
*/
protected void setToHighPriority() {
sendPresence(Presence.Type.AVAILABLE, null, 10, Presence.Mode.AVAILABLE);
}
/**
* Set back the priority to normal level. Used for disabling collabration
*/
protected void setToLowPriority() {
sendPresence(Presence.Type.AVAILABLE, null, 1, Presence.Mode.AVAILABLE);
}
/**
* @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
*/
public String getOnlineTime() {
long diff = System.currentTimeMillis() - connectionTimestamp;
return new Integer((int) diff / 1000 / 60).toString();
}
}