/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.sip.communicator.impl.protocol.icq; import java.util.*; import java.util.Map.Entry; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import net.kano.joustsim.*; import net.kano.joustsim.oscar.oscar.service.chatrooms.*; /** * Represents an ad-hoc chat room, where multiple chat users could communicate * in a many-to-many fashion. * * @author Valentin Martinet */ public class AdHocChatRoomIcqImpl implements AdHocChatRoom { private static final Logger logger = Logger.getLogger(AdHocChatRoomIcqImpl.class); /** * Listeners that will be notified of changes in member status in the room * such as member joined, left or being kicked or dropped. */ private Vector memberListeners = new Vector(); /** * Listeners that will be notified every time a new message is received on * this chat room. */ private Vector messageListeners = new Vector(); /** * Chat room invitation from the icq provider, needed for joining a chat * room. */ private ChatInvitation chatInvitation = null; /** * Chat room session from the icq provider, we get this after joining a chat * room. Provides most of the function we need for multi user chatting. */ private ChatRoomSession chatRoomSession = null; /** * The list of participants of this ad-hoc chat room. */ private Hashtable participants = new Hashtable(); /** * The operation set that created us. */ private OperationSetAdHocMultiUserChatIcqImpl opSetMuc = null; /** * The protocol provider that created us */ private ProtocolProviderServiceIcqImpl provider = null; /** * List with invitations. */ private Hashtable inviteUserList = new Hashtable(); /** * HTML mime type */ private static final String HTML_MIME_TYPE = "text/html"; private final String defaultHtmlStartTag = ""; private final String defaultHtmlEndTag = ""; /** * Chat room name. */ private String chatRoomName = ""; /** * The nick name of the user. */ private String nickName = ""; /** * Chat room subject. Note: ICQ does not support chat room subjects. */ private String chatSubject = ""; /** * Constructor for chat room instances, with a given chat room invitation. * If this constructor is used the user was invited to a chat room. * * @param chatInvitation Chat room invitation that the user received from * the ICQ network * @param icqProvider The ICQ provider */ public AdHocChatRoomIcqImpl(ChatInvitation chatInvitation, ProtocolProviderServiceIcqImpl icqProvider) { chatRoomName = chatInvitation.getRoomName(); this.chatInvitation = chatInvitation; this.provider = icqProvider; this.opSetMuc = (OperationSetAdHocMultiUserChatIcqImpl) provider .getOperationSet(OperationSetAdHocMultiUserChat.class); } /** * Constructor for chat room instances. * * @param roomName The name of the chat room. * @param chatRoomSession Chat room session from the icq network * @param icqProvider The icq provider */ public AdHocChatRoomIcqImpl(String roomName, ChatRoomSession chatRoomSession, ProtocolProviderServiceIcqImpl icqProvider) { this.chatRoomSession = chatRoomSession; chatRoomName = roomName; this.provider = icqProvider; this.opSetMuc = (OperationSetAdHocMultiUserChatIcqImpl) provider .getOperationSet(OperationSetAdHocMultiUserChat.class); this.chatRoomSession.addListener(new AdHocChatRoomSessionListenerImpl( this)); } /** * Adds a listener that will be notified of changes in our status in the * room such as us being kicked, banned, or granted admin permissions. * * @param listener a participant status listener. */ public void addParticipantPresenceListener( AdHocChatRoomParticipantPresenceListener listener) { synchronized (memberListeners) { if (!memberListeners.contains(listener)) memberListeners.add(listener); } } /** * Registers listener so that it would receive events every time a * new message is received on this chat room. * * @param listener a MessageListener that would be notified every * time a new message is received on this chat room. */ public void addMessageListener(AdHocChatRoomMessageListener listener) { synchronized (messageListeners) { if (!messageListeners.contains(listener)) messageListeners.add(listener); } } /** * Create a Message instance for sending arbitrary MIME-encoding content. * * @param content content value * @param contentType the MIME-type for content * @param contentEncoding encoding used for content * @param subject a String subject or null for now * subject. * @return the newly created message. */ public Message createMessage(byte[] content, String contentType, String contentEncoding, String subject) { return new MessageIcqImpl(new String(content), contentType, contentEncoding, subject); } /** * Create a Message instance for sending a simple text messages with default * (text/plain) content type and encoding. * * @param messageText the string content of the message. * @return Message the newly created message */ public Message createMessage(String messageText) { Message msg = new MessageIcqImpl(messageText, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE, OperationSetBasicInstantMessaging.DEFAULT_MIME_ENCODING, null); return msg; } /** * Returns the identifier of this AdHocChatRoom. * * @return a String containing the identifier of this * AdHocChatRoom. */ public String getIdentifier() { return chatRoomName; } /** * Returns a List of Contacts corresponding to all * participants currently participating in this room. * * @return a List of Contact corresponding to all room * participants. */ public List getParticipants() { return new LinkedList(participants.values()); } /** * Returns the number of participants that are currently in this chat room. * * @return the number of Contacts, currently participating in this * room. */ public int getParticipantsCount() { return participants.size(); } /** * Returns the name of this AdHocChatRoom. * * @return a String containing the name of this ChatRoom. */ public String getName() { return chatRoomName; } /** * Returns the protocol provider service that created us. * * @return the protocol provider service that created us. */ public ProtocolProviderService getParentProvider() { return provider; } /** * Returns the last known room subject/theme or null if the user * hasn't joined the room or the room does not have a subject yet. *

* To be notified every time the room's subject change you should add a * ChatRoomPropertyChangelistener to this room. *

* * @return the room subject or null if the user hasn't joined the * room or the room does not have a subject yet. */ public String getSubject() { return chatSubject; } /** * Returns the local user's nickname in the context of this chat room or * null if not currently joined. * * @return the nickname currently being used by the local user in the * context of the local chat room. */ public String getUserNickname() { if (nickName == null) nickName = provider.getInfoRetreiver().getNickName( provider.getAccountID().getUserID()); return nickName; } /** * Invites another user to this room. If we're not joined nothing will * happen. * * @param userAddress the address of the user (email address) to invite to * the room.(one may also invite users not on their contact * list). * @param reason invitation message */ public void invite(String userAddress, String reason) { assertConnected(); if (logger.isInfoEnabled()) logger.info("Inviting " + userAddress + " for reason: " + reason); if (chatRoomSession.getState().equals(ChatSessionState.INROOM)) chatRoomSession.invite(new Screenname(userAddress), reason); else inviteUserList.put(userAddress, reason); } /** * Joins this chat room with the nickname of the local user so that the user * would start receiving events and messages for it. * * @throws OperationFailedException with the corresponding code if an error * occurs while joining the room. */ public void join() throws OperationFailedException { if (chatRoomSession == null && chatInvitation == null) { // the session is not set and we don't have a chatInvitatoin // so we try to join the chatRoom again ChatRoomManager chatRoomManager = provider.getAimConnection().getChatRoomManager(); chatRoomSession = chatRoomManager.joinRoom(this.getName()); chatRoomSession.addListener(new AdHocChatRoomSessionListenerImpl( this)); } else if (chatInvitation != null) { chatRoomSession = chatInvitation.accept(); chatRoomSession.addListener(new AdHocChatRoomSessionListenerImpl( this)); } // We don't specify a reason. opSetMuc.fireLocalUserPresenceEvent(this, LocalUserAdHocChatRoomPresenceChangeEvent.LOCAL_USER_JOINED, null); } /** * Leave this chat room. Once this method is called, the user won't be * listed as a member of the chat room any more and no further chat events * will be delivered. Depending on the underlying protocol and * implementation leave() might cause the room to be destroyed if it has * been created by the local user. */ public void leave() { if (chatRoomSession != null) { // manually close the chat room session // and set the chat room session to null. chatRoomSession.close(); chatRoomSession = null; } Iterator> membersSet = participants.entrySet().iterator(); while (membersSet.hasNext()) { Map.Entry memberEntry = membersSet.next(); Contact participant = memberEntry.getValue(); fireParticipantPresenceEvent(participant, AdHocChatRoomParticipantPresenceChangeEvent.CONTACT_LEFT, "Local user has left the chat room."); } // Delete the list of members participants.clear(); } /** * Removes a listener that was being notified of changes in the status of * other ad-hoc chat room participants. * * @param listener a participant status listener. */ public void removeParticipantPresenceListener( AdHocChatRoomParticipantPresenceListener listener) { synchronized (memberListeners) { memberListeners.remove(listener); } } /** * Removes listener so that it won't receive any further message * events from this room. * * @param listener the MessageListener to remove from this room */ public void removeMessageListener(AdHocChatRoomMessageListener listener) { synchronized (messageListeners) { messageListeners.remove(listener); } } /** * Sends the message to the destination indicated by the * to contact. * * @param message The Message to send. * @throws OperationFailedException if the underlying stack is not * registered or initialized or if the chat room is not joined. */ public void sendMessage(Message message) throws OperationFailedException { assertConnected(); try { chatRoomSession.sendMessage(message.getContent()); // we don't need to fire a message delivered event, because // we will receive this message again. AdHocChatRoomMessageDeliveredEvent msgDeliveredEvt = new AdHocChatRoomMessageDeliveredEvent( this, System.currentTimeMillis(), message, AdHocChatRoomMessageDeliveredEvent.CONVERSATION_MESSAGE_DELIVERED); fireMessageEvent(msgDeliveredEvt); } catch (Exception e) { if (logger.isDebugEnabled()) logger.debug("Failed to send a conference message."); throw new OperationFailedException( "Failed to send a conference message.", OperationFailedException.GENERAL_ERROR); } } /** * Notifies all interested listeners that a * ChatRoomMessageDeliveredEvent, * ChatRoomMessageReceivedEvent or a * ChatRoomMessageDeliveryFailedEvent has been fired. * * @param evt The specific event */ public void fireMessageEvent(EventObject evt) { Iterator listeners = null; synchronized (messageListeners) { listeners = new ArrayList(messageListeners) .iterator(); } while (listeners.hasNext()) { AdHocChatRoomMessageListener listener = listeners.next(); if (evt instanceof AdHocChatRoomMessageDeliveredEvent) { listener .messageDelivered((AdHocChatRoomMessageDeliveredEvent) evt); } else if (evt instanceof AdHocChatRoomMessageReceivedEvent) { listener .messageReceived((AdHocChatRoomMessageReceivedEvent) evt); } else if (evt instanceof AdHocChatRoomMessageDeliveryFailedEvent) { listener .messageDeliveryFailed((AdHocChatRoomMessageDeliveryFailedEvent) evt); } } } /** * Creates the corresponding AdHocChatRoomParticipantPresenceChangeEvent and * notifies all AdHocChatRoomParticipantPresenceListeners that a * Contact has joined or left this AdHocChatRoom. * * @param member the Contact that this * @param eventID the identifier of the event * @param eventReason the reason of the event */ private void fireParticipantPresenceEvent(Contact member, String eventID, String eventReason) { AdHocChatRoomParticipantPresenceChangeEvent evt = new AdHocChatRoomParticipantPresenceChangeEvent(this, member, eventID, eventReason); if (logger.isTraceEnabled()) logger.trace("Will dispatch the following ChatRoom event: " + evt); Iterator listeners = null; synchronized (memberListeners) { listeners = new ArrayList( memberListeners).iterator(); } while (listeners.hasNext()) { AdHocChatRoomParticipantPresenceListener listener = listeners.next(); listener.participantPresenceChanged(evt); } } /** * Our listener for all events for this chat room, e.g. incoming messages or * users that leave or join the chat room. * */ private class AdHocChatRoomSessionListenerImpl implements ChatRoomSessionListener { /** * The chat room this listener is for. */ private AdHocChatRoomIcqImpl chatRoom = null; /** * Constructor for this listener, needed to set the chatRoom. * * @param room The containing chat room. */ public AdHocChatRoomSessionListenerImpl(AdHocChatRoomIcqImpl room) { chatRoom = room; } /** * Handles incoming messages for the specified chat room. * * @param chatRoomSession Specific chat room session * @param chatRoomUser The User who sends the message * @param chatMessage The message */ public void handleIncomingMessage(ChatRoomSession chatRoomSession, ChatRoomUser chatRoomUser, ChatMessage chatMessage) { if (logger.isDebugEnabled()) logger.debug("Incoming multi user chat message received: " + chatMessage.getMessage()); String msgBody = chatMessage.getMessage(); String msgContent; if (msgBody.startsWith(defaultHtmlStartTag)) { msgContent = msgBody.substring(msgBody.indexOf(defaultHtmlStartTag) + defaultHtmlStartTag.length(), msgBody .indexOf(defaultHtmlEndTag)); } else msgContent = msgBody; Message newMessage = createMessage( msgContent.getBytes(), HTML_MIME_TYPE, OperationSetBasicInstantMessagingIcqImpl .DEFAULT_MIME_ENCODING, null); String participantUID = chatRoomUser.getScreenname().getFormatted(); if (participantUID.equals(nickName)) return; AdHocChatRoomMessageReceivedEvent msgReceivedEvent = new AdHocChatRoomMessageReceivedEvent( chatRoom, participants.get(participantUID), System.currentTimeMillis(), newMessage, AdHocChatRoomMessageReceivedEvent .CONVERSATION_MESSAGE_RECEIVED); fireMessageEvent(msgReceivedEvent); } public void handleStateChange(ChatRoomSession chatRoomSession, ChatSessionState oldChatSessionState, ChatSessionState newChatSessionState) { if (logger.isDebugEnabled()) logger.debug("ChatRoomSessionState changed to: " + newChatSessionState); if (chatInvitation == null && newChatSessionState.equals(ChatSessionState.INROOM)) { try { chatRoom.join(); } catch (Exception e) { if (logger.isDebugEnabled()) logger.debug("Failed to join the chat room: " + e); } } if (inviteUserList != null && newChatSessionState.equals(ChatSessionState.INROOM)) { Iterator> invitesIter = inviteUserList.entrySet().iterator(); while (invitesIter.hasNext()) { Map.Entry entry = invitesIter.next(); chatRoom.invite(entry.getKey(), entry.getValue()); } } if (newChatSessionState.equals(ChatSessionState.CLOSED) || newChatSessionState.equals(ChatSessionState.FAILED)) { // the chatRoom is closed or we failed to join, so we remove all // chat room user from the chat room member list updateMemberList(chatRoomSession.getUsers(), true); } } public void handleUsersJoined(ChatRoomSession chatRoomSession, Set chatRoomUserSet) { // add the new members to the member list updateMemberList(chatRoomUserSet, false); } public void handleUsersLeft(ChatRoomSession chatRoomSession, Set chatRoomUserSet) { // remove the given members from the member list updateMemberList(chatRoomUserSet, true); } } /** * Updates the member list, if the given boolean is true given members will * be added, if it is false the given members will be removed. * * @param chatRoomUserSet New members or members to remove * @param removeMember True if members should be removed, False if members * should be added. */ private void updateMemberList(Set chatRoomUserSet, boolean removeMember) { Iterator it = chatRoomUserSet.iterator(); while (it.hasNext()) { ChatRoomUser user = it.next(); String uid = user.getScreenname().getFormatted(); // we want to add a member and he/she is not in our member list if (!removeMember && !participants.containsKey(uid) && !uid.equals(provider.getAccountID().getUserID())) { OperationSetPersistentPresenceIcqImpl presenceOpSet = (OperationSetPersistentPresenceIcqImpl) getParentProvider() .getOperationSet(OperationSetPersistentPresence.class); Contact participant = presenceOpSet.getServerStoredContactList() .findContactByScreenName(uid); participants.put(uid, participant); fireParticipantPresenceEvent(participant, AdHocChatRoomParticipantPresenceChangeEvent.CONTACT_JOINED, null); } // we want to remove a member and found him/her in our member list if (removeMember && participants.containsKey(uid)) { Contact participant = participants.get(uid); participants.remove(uid); fireParticipantPresenceEvent(participant, AdHocChatRoomParticipantPresenceChangeEvent.CONTACT_LEFT, null); } } } /** * Utility method throwing an exception if the stack is not properly * initialized. * * @throws java.lang.IllegalStateException if the underlying stack is not * registered and initialized. */ private void assertConnected() throws IllegalStateException { if (provider == null) throw new IllegalStateException( "The provider must be non-null and signed on the " + "service before being able to communicate."); if (!provider.isRegistered()) throw new IllegalStateException( "The provider must be signed on the service before " + "being able to communicate."); } /** * Finds the member of this chat room corresponding to the given nick name. * * @param nickName the nick name to search for. * @return the member of this chat room corresponding to the given nick * name. */ public Contact findParticipantForNickName(String nickName) { return participants.get(nickName); } }