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.31 $ * Initial Date: 14.10.2004 * Last modified: $Date: 2005/02/14 17:44:20 $ * * @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 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; private boolean isConnected; private boolean flashClientIsRunning = false; private boolean showOfflineBuddies = true; /** * * @param username * @param password */ protected InstantMessagingClient(String username, String password) { this.username = username; this.password = password; jabberServer = InstantMessagingModule.getServername(); connect(); } /** * connect to the JabberServer * */ protected void connect() { 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){ 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()); } } /** * change jabber status. * Example: sendPresencePacket(Presence.Type.AVAILABLE, "at meeting...", 1, Presence.Mode.AWAY); * @param type @see * @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); } }; 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){ if(this.messages.size() > 0 ){ return (Message)this.messages.get(0); }else{ return null; } }else{ return null; } } /** * 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); } /** * @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()); } } /** * 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; } /** * 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(){ 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(messageFormatted); 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; } /** * @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; } }