diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomInvitationJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomInvitationJabberImpl.java new file mode 100644 index 000000000..023354f77 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomInvitationJabberImpl.java @@ -0,0 +1,65 @@ +/* + * SIP Communicator, 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.jabber; + +import net.java.sip.communicator.service.protocol.*; + +/** + * The Jabber implementation of the ChatRoomInvitation interface. + * + * @author Yana Stamcheva + */ +public class ChatRoomInvitationJabberImpl + implements ChatRoomInvitation +{ + private ChatRoom chatRoom; + + private String inviter; + + private String reason; + + private String password; + + /** + * Creates an invitation for the given targetChatRoom, from the + * given inviter. + * + * @param targetChatRoom the ChatRoom for which the invitation is + * @param inviter the ChatRoomMember, which sent the invitation + * @param reason the reason of the invitation + */ + public ChatRoomInvitationJabberImpl(ChatRoom targetChatRoom, + String inviter, + String reason, + String password) + { + this.chatRoom = targetChatRoom; + this.inviter = inviter; + this.reason = reason; + this.password = password; + } + + public ChatRoom getTargetChatRoom() + { + return chatRoom; + } + + public String getInviter() + { + return inviter; + } + + public String getReason() + { + return reason; + } + + public String getChatRoomPassword() + { + return password; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java index 56fd6be47..66f975271 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java @@ -6,13 +6,19 @@ */ package net.java.sip.communicator.impl.protocol.jabber; +import java.beans.*; import java.util.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*; import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.Message; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.*; +import org.jivesoftware.smackx.*; import org.jivesoftware.smackx.muc.*; /** @@ -32,26 +38,35 @@ public class ChatRoomJabberImpl */ private MultiUserChat multiUserChat = null; + /** + * The configuration form that will be used to change cha room properties. + */ + private Form multiUserChatConfigForm = null; + /** * The list of listeners registered for chat room property change events. */ private Vector chatRoomPropertyChangeListeners = new Vector(); /** - * Listeners that will be notified of changes in our status in the - * room such as us being kicked, banned, or granted admin permissions. + * 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(); /** - * The protocol provider that created us + * Listeners that will be notified of changes in member role in the + * room such as member being granted admin permissions, or revoked admin + * permissions. */ - private ProtocolProviderServiceJabberImpl provider = null; - + private Vector memberRoleListeners = new Vector(); + /** - * The operation set that crated us. + * Listeners that will be notified of changes in local user role in the + * room such as member being granted admin permissions, or revoked admin + * permissions. */ - OperationSetMultiUserChatJabberImpl opSetMuc = null; + private Vector localUserRoleListeners = new Vector(); /** * Listeners that will be notified every time @@ -60,10 +75,46 @@ public class ChatRoomJabberImpl private Vector messageListeners = new Vector(); /** - * The members of this chat room. + * Listeners that will be notified every time + * a chat room property has been changed. + */ + private Vector propertyChangeListeners = new Vector(); + + /** + * The protocol provider that created us + */ + private ProtocolProviderServiceJabberImpl provider = null; + + /** + * The operation set that crated us. + */ + private OperationSetMultiUserChatJabberImpl opSetMuc = null; + + /** + * The list of members of this chat room. */ private Hashtable members = new Hashtable(); + /** + * The list of members of this chat room. + */ + private Hashtable banList = new Hashtable(); + + /** + * The list of advanced configurations for this chat room. + */ + private Hashtable advancedConfigurations = new Hashtable(); + + /** + * The nickname of this chat room local user participant. + */ + private String nickname; + + /** + * The subject of this chat room. Keeps track of the subject changes. + */ + private String oldSubject; + /** * Creates an instance of a chat room that has been. * @@ -75,18 +126,17 @@ public ChatRoomJabberImpl(MultiUserChat multiUserChat, ProtocolProviderServiceJabberImpl provider) { this.multiUserChat = multiUserChat; + this.provider = provider; this.opSetMuc = (OperationSetMultiUserChatJabberImpl)provider .getOperationSet(OperationSetMultiUserChat.class); - - /** @todo add listeners to the chat room */ - /** @todo multiUserChat.addMessageListener(); */ - /** @todo multiUserChat.addParticipantListener();*/ - /** @todo multiUserChat.addParticipantStatusListener(); */ - /** @todo multiUserChat.addPresenceInterceptor(); */ - /** @todo multiUserChat.addSubjectUpdatedListener(); */ - /** @todo multiUserChat.addUserStatusListener(); */ - //multiUserChat.addParticipantStatusListener(this); + + this.oldSubject = multiUserChat.getSubject(); + + multiUserChat.addSubjectUpdatedListener( + new SmackSubjectUpdatedListener()); + multiUserChat.addMessageListener(new SmackMessageListener()); + multiUserChat.addParticipantStatusListener(new MemberListener()); } /** @@ -183,7 +233,6 @@ public void removeMemberPresenceListener( { memberListeners.remove(listener); } - } /** @@ -234,7 +283,7 @@ public Message createMessage(String messageText) */ public List getMembers() { - return new LinkedList(members.entrySet()); + return new LinkedList(members.values()); } /** @@ -292,7 +341,7 @@ public String getUserNickname() */ public String getSubject() { - return this.multiUserChat.getNickname(); + return this.multiUserChat.getSubject(); } /** @@ -361,19 +410,32 @@ public void join() public void joinAs(String nickname, byte[] password) throws OperationFailedException { + this.nickname = nickname; + try { multiUserChat.join(nickname, new String(password)); - - initMembers(); + + ChatRoomMemberJabberImpl member + = new ChatRoomMemberJabberImpl( this, + nickname, + provider.getAccountID() + .getAccountAddress(), + ChatRoomMemberRole.GUEST); + + members.put(nickname, member); + + // We don't specify a reason. + opSetMuc.fireLocalUserPresenceEvent(this, + LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED, null); } catch (XMPPException ex) { logger.error("Failed to join chat room " + getName() + " with nickname " - + nickname - , ex ); + + nickname, + ex ); } } @@ -388,11 +450,34 @@ public void joinAs(String nickname, byte[] password) public void joinAs(String nickname) throws OperationFailedException { + this.nickname = nickname; + try { multiUserChat.join(nickname); - initMembers(); + try + { + this.multiUserChatConfigForm + = multiUserChat.getConfigurationForm(); + } + catch (XMPPException e) + { + logger.error("", e); + } + + ChatRoomMemberJabberImpl member + = new ChatRoomMemberJabberImpl( this, + nickname, + provider.getAccountID() + .getAccountAddress(), + ChatRoomMemberRole.GUEST); + + members.put(nickname, member); + + //we don't specify a reason + opSetMuc.fireLocalUserPresenceEvent(this, + LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED, null); } catch (XMPPException ex) { @@ -411,34 +496,6 @@ public void joinAs(String nickname) } } - /** - * Initialises the list of chat room members. - * - * @throws XMPPException if we fail retrieving the members. - */ - private void initMembers() - throws XMPPException - { - Iterator occupantsIter = multiUserChat.getOccupants(); - - while (occupantsIter.hasNext()) - { - String occupantStr = (String) occupantsIter.next(); - - Occupant occupant = multiUserChat.getOccupant(occupantStr); - - ChatRoomMemberRole role = smackRoleToScRole(occupant.getRole()); - - //smack returns fully qualified occupant names. - ChatRoomMemberJabberImpl member = new ChatRoomMemberJabberImpl( - this - , occupant.getNick() - , occupant.getJid() - , role); - this.members.put(member.getContactAddress(), member); - } - } - /** * Returns that ChatRoomJabberRole instance corresponding to the * smackRole string. @@ -463,13 +520,55 @@ else if (smackRole.equalsIgnoreCase("participant")) } } + /** + * Returns the ChatRoomMember corresponding to the given smack + * participant. + * + * @param participant the full participant name + * (e.g. sc-testroom@conference.voipgw.u-strasbg.fr/testuser) + * @return the ChatRoomMember corresponding to the given smack + * participant + */ + public ChatRoomMember smackParticipantToScMember(String participant) + { + String participantName = StringUtils.parseResource(participant); + + Iterator chatRoomMembers = this.members.values().iterator(); + + while(chatRoomMembers.hasNext()) + { + ChatRoomMember member = (ChatRoomMember) chatRoomMembers.next(); + + if(participantName.equals(member.getName()) + || participant.equals(member.getContactAddress())) + return member; + } + + return null; + } + /** * Leave this chat room. - * */ public void leave() { multiUserChat.leave(); + + Iterator membersSet = members.entrySet().iterator(); + + while(membersSet.hasNext()) + { + Map.Entry memberEntry = (Map.Entry) membersSet.next(); + + ChatRoomMember member = (ChatRoomMember) memberEntry.getValue(); + + fireMemberPresenceEvent(member, + ChatRoomMemberPresenceChangeEvent.MEMBER_LEFT, + "Local user has left the chat room."); + } + + // Delete the list of members + members.clear(); } /** @@ -483,18 +582,37 @@ public void leave() public void sendMessage(Message message) throws OperationFailedException { - try - { - multiUserChat.sendMessage(message.getContent()); - } - catch (XMPPException ex) - { - logger.error("Failed to send message " + message, ex); - throw new OperationFailedException( - "Failed to send message " + message - , OperationFailedException.GENERAL_ERROR - , ex); - } + try + { + assertConnected(); + + org.jivesoftware.smack.packet.Message msg = + new org.jivesoftware.smack.packet.Message(); + + msg.setBody(message.getContent()); + msg.addExtension(new Version()); + + MessageEventManager. + addNotificationsRequests(msg, true, false, false, true); + + // We send only the content because it doesn't work if we send the + // Message object. + multiUserChat.sendMessage(message.getContent()); + + ChatRoomMessageDeliveredEvent msgDeliveredEvt + = new ChatRoomMessageDeliveredEvent( + this, new Date(), message); + + fireMessageEvent(msgDeliveredEvt); + } + catch (XMPPException ex) + { + logger.error("Failed to send message " + message, ex); + throw new OperationFailedException( + "Failed to send message " + message + , OperationFailedException.GENERAL_ERROR + , ex); + } } /** @@ -530,7 +648,7 @@ public void setNickname(String nickname) */ public void setSubject(String subject) throws OperationFailedException - { + { try { multiUserChat.changeSubject(subject); @@ -544,7 +662,6 @@ public void setSubject(String subject) , OperationFailedException.FORBIDDEN , ex); } - } /** @@ -578,7 +695,18 @@ private class MemberListener implements ParticipantStatusListener */ public void banned(String participant, String actor, String reason) { - /** @todo implement banned() */ + logger.info(participant + " has been banned from " + + getName() + " chat room."); + + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + banList.put(participant, member); + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.OUTCAST); } /** @@ -591,7 +719,13 @@ public void banned(String participant, String actor, String reason) */ public void adminGranted(String participant) { - /** @todo implement banned() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.ADMINISTRATOR); } /** @@ -605,7 +739,13 @@ public void adminGranted(String participant) */ public void adminRevoked(String participant) { - /** @todo implement banned() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.MEMBER); } /** @@ -619,7 +759,31 @@ public void adminRevoked(String participant) */ public void joined(String participant) { - /** @todo implement joined() */ + logger.info(participant + " has joined the " + + getName() + " chat room."); + + String participantName = StringUtils.parseResource(participant); + + if(participantName.equals(nickname) + || members.containsKey(participantName)) + return; + + Occupant occupant = multiUserChat.getOccupant(participant); + + ChatRoomMemberRole role = smackRoleToScRole(occupant.getRole()); + + //smack returns fully qualified occupant names. + ChatRoomMemberJabberImpl member = new ChatRoomMemberJabberImpl( + ChatRoomJabberImpl.this, + occupant.getNick(), + occupant.getJid(), + role); + + members.put(participantName, member); + + //we don't specify a reason + fireMemberPresenceEvent(member, + ChatRoomMemberPresenceChangeEvent.MEMBER_JOINED, null); } /** @@ -631,7 +795,21 @@ public void joined(String participant) */ public void left(String participant) { - /** @todo implement left() */ + logger.info(participant + " has left the " + + getName() + " chat room."); + + ChatRoomMember member + = smackParticipantToScMember(participant); + + if(member == null) + return; + + String participantName = StringUtils.parseResource(participant); + + members.remove(participantName); + + fireMemberPresenceEvent(member, + ChatRoomMemberPresenceChangeEvent.MEMBER_LEFT, null); } /** @@ -659,7 +837,13 @@ public void nicknameChanged(String participant, String newNickname) */ public void ownershipRevoked(String participant) { - /** @todo implement ownershipRevoked() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.MEMBER); } /** @@ -676,7 +860,21 @@ public void ownershipRevoked(String participant) */ public void kicked(String participant, String actor, String reason) { - /** @todo implement kicked() */ + ChatRoomMember member + = smackParticipantToScMember(participant); + + ChatRoomMember actorMember + = smackParticipantToScMember(actor); + + if(member == null) + return; + + String participantName = StringUtils.parseResource(participant); + + members.remove(participantName); + + fireMemberPresenceEvent(member, actorMember, + ChatRoomMemberPresenceChangeEvent.MEMBER_KICKED, reason); } /** @@ -690,7 +888,13 @@ public void kicked(String participant, String actor, String reason) */ public void moderatorGranted(String participant) { - /** @todo implement moderatorGranted() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.MODERATOR); } /** @@ -703,7 +907,13 @@ public void moderatorGranted(String participant) */ public void voiceRevoked(String participant) { - /** @todo implement voiceRevoked() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.SILENT_MEMBER); } /** @@ -715,7 +925,13 @@ public void voiceRevoked(String participant) */ public void membershipGranted(String participant) { - /** @todo implement membershipGranted() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.MEMBER); } /** @@ -729,7 +945,13 @@ public void membershipGranted(String participant) */ public void moderatorRevoked(String participant) { - /** @todo implement moderatorRevoked() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.GUEST); } /** @@ -742,7 +964,13 @@ public void moderatorRevoked(String participant) */ public void voiceGranted(String participant) { - /** @todo implement voiceGranted() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.GUEST); } /** @@ -756,7 +984,13 @@ public void voiceGranted(String participant) */ public void membershipRevoked(String participant) { - /** @todo implement membershipRevoked() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.GUEST); } /** @@ -769,282 +1003,1112 @@ public void membershipRevoked(String participant) */ public void ownershipGranted(String participant) { - /** @todo implement ownershipGranted() */ + ChatRoomMember member = smackParticipantToScMember(participant); + + if(member == null) + return; + + fireMemberRoleEvent(member, member.getRole(), + ChatRoomMemberRole.OWNER); } } + /** + * Adds a listener that will be notified of changes in our role in the room + * such as us being granded operator. + * + * @param listener a local user role listener. + */ public void addLocalUserRoleListener( ChatRoomLocalUserRoleListener listener) { - /** @todo implement addLocalUserRoleListener() */ + synchronized(localUserRoleListeners) + { + if (!localUserRoleListeners.contains(listener)) + localUserRoleListeners.add(listener); + } } + /** + * Removes a listener that was being notified of changes in our role in this + * chat room such as us being granded operator. + * + * @param listener a local user role listener. + */ public void removelocalUserRoleListener( ChatRoomLocalUserRoleListener listener) { - /** @todo implement removeLocalUserRoleListener() */ + synchronized(localUserRoleListeners) + { + localUserRoleListeners.remove(listener); + } } + /** + * Adds a listener that will be notified of changes of a member role in the + * room such as being granded operator. + * + * @param listener a member role listener. + */ public void addMemberRoleListener(ChatRoomMemberRoleListener listener) { - /** @todo implement addMemberRoleListener() */ + synchronized(memberRoleListeners) + { + if (!memberRoleListeners.contains(listener)) + memberRoleListeners.add(listener); + } } + /** + * Removes a listener that was being notified of changes of a member role in + * this chat room such as us being granded operator. + * + * @param listener a member role listener. + */ public void removeMemberRoleListener(ChatRoomMemberRoleListener listener) { - /** @todo implement removeMemberRoleListener() */ + synchronized(memberRoleListeners) + { + memberRoleListeners.remove(listener); + } } + /** + * Sets the password of this chat room. If the user does not have the right + * to change the room password, or the protocol does not support this, or + * the operation fails for some other reason, the method throws an + * OperationFailedException with the corresponding code. + * + * @param password the new password that we'd like this room to have + * @throws OperationFailedException if the user does not have the right to + * change the room password, or the protocol does not support + * this, or the operation fails for some other reason + */ public void setPassword(String password) throws OperationFailedException { - /** @todo implement setPassword */ + multiUserChatConfigForm.setAnswer("password", password); + + try + { + multiUserChat.sendConfigurationForm(multiUserChatConfigForm); + } + catch (XMPPException e) + { + logger.error( + "Failed to send configuration form for this chat room.", e); + } } public String getPassword() { - /** @todo implement getPassword */ - return null; + return (String) multiUserChatConfigForm + .getField("password").getValues().next(); } - public void addBanMask(String banMask) throws OperationFailedException + public void addBanMask(String banMask) + throws OperationFailedException { /** @todo implement addBanMask */ } - public void removeBanMask(String banMask) throws OperationFailedException + public void removeBanMask(String banMask) + throws OperationFailedException { /** @todo implement removeBanMask */ } - public void setUserLimit(int userLimit) throws OperationFailedException - { - /** @todo implement setUserLimit */ - } - - public int getUserLimit() - { - /** @todo implement getUserLimit */ - return 0; - } - + /** + * Returns the list of banned users. + */ public Iterator getBanList() - { - /** @todo implement getBanList */ - return null; + throws OperationFailedException + { + return banList.values().iterator(); } - public void setUserNickname(String nickname) throws OperationFailedException + /** + * Changes the local user nickname. If the new nickname already exist in the + * chat room throws an OperationFailedException. + * + * @param nickname the new nickname within the room. + * + * @throws OperationFailedException if the new nickname already exist in + * this room + */ + public void setUserNickname(String nickname) + throws OperationFailedException { - /** @todo implement setUserNickname */ + try + { + multiUserChat.changeNickname(nickname); + } + catch (XMPPException e) + { + throw new OperationFailedException("The " + nickname + + "already exists in this chat room.", + OperationFailedException.NICKNAME_ALREADY_EXISTS); + } } + /** + * Returns true by default. The constant value indicates that the chat room + * visibility could not be changed. + */ public boolean isVisible() - { + { return true; } + /** + * Fires the appropriare ChatRoomPropertyChangeFailedEvent to indicate that + * this property is not supproted by this implementation. + * + * @param isVisible indicates if this ChatRoom should be visible or + * not + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setVisible(boolean isVisible) + throws OperationFailedException { - /** @todo implement setVisible */ - } - - public boolean isPasswordRequired() - { - return false; - } - - public void setPasswordRequired(boolean isPasswordRequired) - { - /** @todo implement setPasswordRequired */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_VISIBLE, + null, + new Boolean(isVisible), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isVisible property is not supported by the jabber multi" + + " user chat implementation.")); } + /** + * Returns false by default. The constant indicates that the chat room + * doesn't require invitation and this property is not supported by this + * implementation. + */ public boolean isInvitationRequired() { return false; } + /** + * Fires the appropriare ChatRoomPropertyChangeFailedEvent to indicate that + * this property is not supproted by this implementation. + * + * @param isInvitationRequired indicates if this ChatRoom requires + * invitation to be joined + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setInvitationRequired(boolean isInvitationRequired) + throws OperationFailedException { - /** @todo implement setInvitationRequired */ - } - - public boolean isMemberNumberLimited() - { - return false; - } - - public void setMemberNumberLimited(boolean isMemberNumberLimited) - { - /** @todo implement setMemberNumberLimited */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_INVITATION_REQUIRED, + null, + new Boolean(isInvitationRequired), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isInvitationRequired property is not supported by the jabber" + + " multi user chat implementation.")); } + /** + * Returns false by default. The constant indicates that this property could + * not be modified in this implementation. + */ public boolean isMute() { return false; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isMute indicates if this ChatRoom should be in a mute + * mode or not + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setMute(boolean isMute) + throws OperationFailedException { - /** @todo implement setMute */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_MUTE, + null, + new Boolean(isMute), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isMute property is not supported by the jabber" + + " multi user chat implementation.")); } + /** + * Returns false, which indicates that this property could not be modified + * in this implementation. + */ public boolean isAllowExternalMessages() { return false; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isAllowExternalMessages indicates if this ChatRoom should + * allow external messages or not + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setAllowExternalMessages(boolean isAllowExternalMessages) + throws OperationFailedException { - /** @todo implement setAllowExternalMessages */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_ALLOW_EXTERNAL_MESSAGES, + null, + new Boolean(isAllowExternalMessages), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isMute property is not supported by the jabber" + + " multi user chat implementation.")); } + /** + * Returns false, which indicates that this property could not be modified + * in this implementation. + */ public boolean isRegistered() { return false; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isRegistered indicates if this ChatRoom is registered + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setRegistered(boolean isRegistered) + throws OperationFailedException { - /** @todo implement setRegistered */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_REGISTERED, + null, + new Boolean(isRegistered), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isMute property is not supported by the jabber" + + " multi user chat implementation.")); } + /** + * Returns false by default. The constant indicates that property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isSubjectLocked() { return false; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isSubjectLocked indicates if the subject of this chat room could + * be changed + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setSubjectLocked(boolean isSubjectLocked) + throws OperationFailedException { - /** @todo implement setSubjectLocked */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_SUBJECT_LOCKED, + null, + new Boolean(isSubjectLocked), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isSubjectLocked property is not supported by the jabber" + + "multi user chat implementation.")); } + /** + * Returns true by default. The constant indicates that property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isAllowMessageFormatting() { return true; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isAllowMessageFormatting indicates if message formattings are + * allowed in this chat room + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setAllowMessageFormatting(boolean isAllowMessageFormatting) + throws OperationFailedException { - /** @todo implement setAllowMessageFormatting */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_ALLOW_MESSAGE_FORMATTING, + null, + new Boolean(isAllowMessageFormatting), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isAllowMessageFormatting property is not supported by the jabber" + + "multi user chat implementation.")); } + /** + * Returns false by default. The constant indicates that property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isFilterMessageFormatting() { return false; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isFilterMessageFormatting indicates if message formattings are + * filtered in this chat room + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setFilterMessageFormatting(boolean isFilterMessageFormatting) + throws OperationFailedException { - /** @todo implement setFilterMessageFormatting */ - } - - public boolean isJoinTimeLimited() - { - return false; - } - - public void setJoinTimeLimited(boolean isJoinTimeLimited) - { - /** @todo implement setJoinTimeLimited */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_FILTER_MESSAGE_FORMATTING, + null, + new Boolean(isFilterMessageFormatting), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isFilterMessageFormatting property is not supported by the jabber" + + "multi user chat implementation.")); } + /** + * Returns true by default. The constant indicates that property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isAllowInvitation() { return true; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isAllowInvitation indicates if invitations are allowed in this + * chat room + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setAllowInvitation(boolean isAllowInvitation) + throws OperationFailedException { - /** @todo implement setAllowInvitationSend */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_ALLOW_INVITATION, + null, + new Boolean(isAllowInvitation), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isAllowInvitation property is not supported by the jabber" + + "multi user chat implementation.")); } + /** + * Returns true by default. The constant indicates that property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isAllowInvitationRequest() { return true; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isAllowInvitationRequest indicates if invitation request are + * allowed in this chat room + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setAllowInvitationRequest(boolean isAllowInvitationRequest) + throws OperationFailedException { - /** @todo implement setAllowInvitationReceive */ - } - - public boolean isUserRedirected() - { - return false; - } - - public void setUserRedirected(boolean isRedirected) - { - /** @todo implement setUserRedirected */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_ALLOW_INVITATION_REQUEST, + null, + new Boolean(isAllowInvitationRequest), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isAllowInvitationRequest property is not supported by the jabber" + + "multi user chat implementation.")); } - public boolean isUserNicknameLocked() + /** + * Returns false by default. The constant indicates that property is not + * configurable in this jabber ChatRoom implementation. + */ + public boolean isMemberNicknameLocked() { return false; } - public void setUserNicknameLocked(boolean isUserNicknameLocked) + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isMemberNicknameLocked indicates if a member in this chat room + * could change his/her nickname + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ + public void setMemberNicknameLocked(boolean isMemberNicknameLocked) + throws OperationFailedException { - /** @todo implement setUserNicknameLocked */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_MEMBER_NICKNAME_LOCKED, + null, + new Boolean(isMemberNicknameLocked), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isMemberNicknameLocked property is not supported by the jabber" + + "multi user chat implementation.")); } + /** + * Returns true by default. The constant indicates that property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isAllowKick() { - return false; + return true; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isAllowKick indicates if this chat room allows kicks + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setAllowKick(boolean isAllowKick) - { - /** @todo implement setAllowKick */ + throws OperationFailedException + { + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_ALLOW_KICK, + null, + new Boolean(isAllowKick), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isAllowKick property is not supported by the jabber" + + "multi user chat implementation.")); } + /** + * Returns false to indicate that isRegisteredUsersOnly property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isRegisteredUserOnly() { return false; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isRegisteredUsersOnly indicates if this chat room is only for + * registered users. + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setRegisteredUserOnly(boolean isRegisteredUserOnly) + throws OperationFailedException { - /** @todo implement setRegisteredUserOnly*/ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_REGISTERED_USERS_ONLY, + null, + new Boolean(isRegisteredUserOnly), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isRegisteredUserOnly property is not supported by the jabber" + + "multi user chat implementation.")); } + /** + * Returns false to indicate that isAllowSpecialMessage property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isAllowSpecialMessage() { return false; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isAllowSpecialMessage indicates if special messages are allowed + * in this chat room. + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setAllowSpecialMessage(boolean isAllowSpecialMessage) + throws OperationFailedException { - /** @todo implement setAllowSpecialMessage() */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_ALLOW_SPECIAL_MESSAGE, + null, + new Boolean(isAllowSpecialMessage), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isAllowSpecialMessage property is not supported by the jabber" + + "multi user chat implementation.")); } + /** + * Returns true to indicate that isNickNameListVisible property is not + * configurable in this jabber ChatRoom implementation. + */ public boolean isNicknameListVisible() { return true; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param isNicknameListVisible indicates if the list of nicknames in this + * chat room should be visible. + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ public void setNicknameListVisible(boolean isNicknameListVisible) + throws OperationFailedException { - /** @todo implement setNicknameListVisible() */ + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_IS_NICKNAME_LIST_VISIBLE, + null, + new Boolean(isNicknameListVisible), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The isNicknameListVisible property is not supported by the jabber" + + "multi user chat implementation.")); + } + + /** + * Returns the limit of user for this chat room. The user limit is the + * maximum number of users, who could enter this chat room at a time. + * + * @return int the max number of users for this chat room + */ + public int getMemberMaxNumber() + { + return 0; + } + + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * + * @param maxNumber the maximum number of users that we'd like this room to + * have + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ + public void setMemberMaxNumber(int maxNumber) + throws OperationFailedException + { + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_MEMBER_MAX_NUMBER, + null, + new Integer(maxNumber), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The redirectChatRoom property is not supported by the jabber multi" + + " user chat implementation.")); + } + + /** + * Returns null to indicate that no redirection is possible in the jabber + * ChatRoom implementation. + */ + public ChatRoom getRedirectChatRoom() + { + return null; } + /** + * Fires a ChatRoomPropertyChangeFailedEvent to indicated that + * the jabber ChatRoom implementation doesn't support this property. + * @param chatRoom the chat room to which the messages are redirected + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ + public void setRedirectChatRoom(ChatRoom chatRoom) + throws OperationFailedException + { + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_REDIRECT, + null, + chatRoom, + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The redirectChatRoom property is not supported by the jabber multi" + + " user chat implementation.")); + } + + + /** + * For now we just fire a ChatRoomPropertyChangeFailedEvent + * .PROPERTY_NOT_SUPPORTED to indicate that this property is not supported + * by this implementation. + * @param seconds the period that user should wait before re-joining a + * chat room + * @throws OperationFailedException if the user doesn't have the right to + * change this property. + */ + public void setJoinRateLimit(int seconds) + throws OperationFailedException + { + firePropertyChangeEvent(new ChatRoomPropertyChangeFailedEvent( + this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_JOIN_RATE_LIMIT, + null, + new Integer(seconds), + ChatRoomPropertyChangeFailedEvent.PROPERTY_NOT_SUPPORTED, + "The joinRateLimit property is not supported by the jabber multi" + + " user chat implementation.")); + } + + public int getJoinRateLimit() + { + return 0; + } + + /** + * Adds the given property to the list of advanced configuration + * properties. + * + * @param propertyName the name of the property to add + * @param propertyValue the value of the property to add + */ public void addAdvancedConfigProperty(String propertyName, String propertyValue) throws OperationFailedException { - /** @todo implement addAdvancedConfigProperty() */ + this.advancedConfigurations.put(propertyName, propertyValue); } + /** + * Removes the given property from the list of advanced configuration + * properties. + * + * @param propertyName the property to remove + */ public void removeAdvancedConfigProperty(String propertyName) throws OperationFailedException { - /** @todo implement removeAdvancedConfigProperty() */ + this.advancedConfigurations.remove(propertyName); } - public Map getAdvancedConfigurationSet() + /** + * Returns an Iterator over a copy of the table containing all advanced + * configurations for this ChatRoom. + */ + public Iterator getAdvancedConfigurationSet() { - /** @todo implement getAdvancedConfigurationSet() */ - return null; + return new Hashtable(advancedConfigurations).entrySet().iterator(); + } + + /** + * Bans a user from the room. An admin or owner of the room can ban users + * from a room. + * + * @param chatRoomMember the ChatRoomMember to be banned. + * @param reason the reason why the user was banned. + * @throws OperationFailedException if an error occurs while banning a user. + * In particular, an error can occur if a moderator or a user with an + * affiliation of "owner" or "admin" was tried to be banned or if the user + * that is banning have not enough permissions to ban. + */ + public void banParticipant(ChatRoomMember chatRoomMember, String reason) + throws OperationFailedException + { + try + { + multiUserChat.banUser(chatRoomMember.getContactAddress(), reason); + } + catch (XMPPException e) + { + logger.error("Failed to ban participant.", e); + + // If a moderator or a user with an affiliation of "owner" or "admin" + // was intended to be kicked. + if (e.getXMPPError().getCode() == 405) + { + throw new OperationFailedException( + "Kicking an admin user or a chat room owner is a forbidden " + + "operation.", + OperationFailedException.FORBIDDEN); + } + else + { + throw new OperationFailedException( + "An error occured while trying to kick the participant.", + OperationFailedException.GENERAL_ERROR); + } + } + } + + /** + * Kicks a participant from the room. + * + * @param chatRoomMember the ChatRoomMember to kick from the room + * @param reason the reason why the participant is being kicked from the + * room + * @throws OperationFailedException if an error occurs while kicking the + * participant. In particular, an error can occur if a moderator or a user + * with an affiliation of "owner" or "admin" was intended to be kicked; or + * if the participant that intended to kick another participant does not + * have kicking privileges; + */ + public void kickParticipant(ChatRoomMember member, String reason) + throws OperationFailedException + { + try + { + multiUserChat.kickParticipant(member.getName(), reason); + } + catch (XMPPException e) + { + logger.error("Failed to kick participant.", e); + + // If a moderator or a user with an affiliation of "owner" or "admin" + // was intended to be kicked. + if (e.getXMPPError().getCode() == 405) //not allowed + { + throw new OperationFailedException( + "Kicking an admin user or a chat room owner is a forbidden " + + "operation.", + OperationFailedException.FORBIDDEN); + } + // If a participant that intended to kick another participant does + // not have kicking privileges. + else if (e.getXMPPError().getCode() == 403) //forbidden + { + throw new OperationFailedException( + "The user that intended to kick another participant does" + + " not have enough privileges to do that.", + OperationFailedException.NOT_ENOUGH_PRIVILEGES); + } + else + { + throw new OperationFailedException( + "An error occured while trying to kick the participant.", + OperationFailedException.GENERAL_ERROR); + } + } + } + + /** + * Creates the corresponding ChatRoomMemberPresenceChangeEvent and notifies + * all ChatRoomMemberPresenceListeners that a ChatRoomMember has + * joined or left this ChatRoom. + * + * @param member the ChatRoomMember that this + * @param eventID the identifier of the event + * @param eventReason the reason of the event + */ + private void fireMemberPresenceEvent(ChatRoomMember member, + String eventID, String eventReason) + { + ChatRoomMemberPresenceChangeEvent evt + = new ChatRoomMemberPresenceChangeEvent( + this, member, eventID, eventReason); + + logger.trace("Will dispatch the following ChatRoom event: " + evt); + + Iterator listeners = null; + synchronized (memberListeners) + { + listeners = new ArrayList(memberListeners).iterator(); + } + + while (listeners.hasNext()) + { + ChatRoomMemberPresenceListener listener + = (ChatRoomMemberPresenceListener) listeners.next(); + + listener.memberPresenceChanged(evt); + } + } + + /** + * Creates the corresponding ChatRoomMemberPresenceChangeEvent and notifies + * all ChatRoomMemberPresenceListeners that a ChatRoomMember has + * joined or left this ChatRoom. + * + * @param member the ChatRoomMember that changed its presence + * status + * @param actor the ChatRoomMember that participated as an actor + * in this event + * @param eventID the identifier of the event + * @param eventReason the reason of this event + */ + private void fireMemberPresenceEvent(ChatRoomMember member, + ChatRoomMember actor, String eventID, String eventReason) + { + ChatRoomMemberPresenceChangeEvent evt + = new ChatRoomMemberPresenceChangeEvent( + this, member, actor, eventID, eventReason); + + logger.trace("Will dispatch the following ChatRoom event: " + evt); + + Iterator listeners = null; + synchronized (memberListeners) + { + listeners = new ArrayList(memberListeners).iterator(); + } + + while (listeners.hasNext()) + { + ChatRoomMemberPresenceListener listener + = (ChatRoomMemberPresenceListener) listeners.next(); + + listener.memberPresenceChanged(evt); + } + } + + /** + * Creates the corresponding ChatRoomMemberRoleChangeEvent and notifies + * all ChatRoomMemberRoleListeners that a ChatRoomMember has + * changed its role in this ChatRoom. + * + * @param member the ChatRoomMember that has changed its role + * @param previousRole the previous role that member had + * @param newRole the new role the member get + */ + private void fireMemberRoleEvent(ChatRoomMember member, + ChatRoomMemberRole previousRole, ChatRoomMemberRole newRole) + { + ChatRoomMemberRoleChangeEvent evt + = new ChatRoomMemberRoleChangeEvent( + this, member, previousRole, newRole); + + logger.trace("Will dispatch the following ChatRoom event: " + evt); + + Iterator listeners = null; + synchronized (memberRoleListeners) + { + listeners = new ArrayList(memberRoleListeners).iterator(); + } + + while (listeners.hasNext()) + { + ChatRoomMemberRoleListener listener + = (ChatRoomMemberRoleListener) listeners.next(); + + listener.memberRoleChanged(evt); + } + } + + /** + * Delivers the specified event to all registered message listeners. + * @param evt the EventObject that we'd like delivered to all + * registered message listerners. + */ + private void fireMessageEvent(EventObject evt) + { + Iterator listeners = null; + synchronized (messageListeners) + { + listeners = new ArrayList(messageListeners).iterator(); + } + + while (listeners.hasNext()) + { + ChatRoomMessageListener listener + = (ChatRoomMessageListener) listeners.next(); + + if (evt instanceof ChatRoomMessageDeliveredEvent) + { + listener.messageDelivered( (ChatRoomMessageDeliveredEvent) evt); + } + else if (evt instanceof ChatRoomMessageReceivedEvent) + { + listener.messageReceived( (ChatRoomMessageReceivedEvent) evt); + } + else if (evt instanceof ChatRoomMessageDeliveryFailedEvent) + { + listener.messageDeliveryFailed( + (ChatRoomMessageDeliveryFailedEvent) evt); + } + } + } + + /** + * A listener that listens for packets of type Message and fires an event + * to notifier interesting parties that a message was received. + */ + private class SmackMessageListener + implements PacketListener + { + public void processPacket(Packet packet) + { + if(!(packet instanceof org.jivesoftware.smack.packet.Message)) + return; + + org.jivesoftware.smack.packet.Message msg = + (org.jivesoftware.smack.packet.Message)packet; + + if(msg.getBody() == null) + return; + + String fromUserName = StringUtils.parseResource(msg.getFrom()); + + if(fromUserName.equals(nickname)) + return; + + ChatRoomMember member = smackParticipantToScMember(msg.getFrom()); + + if(logger.isDebugEnabled()) + { + logger.debug("Received from " + + fromUserName + + " the message " + + msg.toXML()); + } + + Message newMessage = createMessage(msg.getBody()); + + if(msg.getType() == org.jivesoftware.smack.packet.Message.Type.error) + { + logger.info("Message error received from " + fromUserName); + + int errorCode = packet.getError().getCode(); + int errorResultCode + = ChatRoomMessageDeliveryFailedEvent.UNKNOWN_ERROR; + + if(errorCode == 503) + { + org.jivesoftware.smackx.packet.MessageEvent msgEvent = + (org.jivesoftware.smackx.packet.MessageEvent) + packet.getExtension("x", "jabber:x:event"); + if(msgEvent != null && msgEvent.isOffline()) + { + errorResultCode = ChatRoomMessageDeliveryFailedEvent + .OFFLINE_MESSAGES_NOT_SUPPORTED; + } + } + + ChatRoomMessageDeliveryFailedEvent evt = + new ChatRoomMessageDeliveryFailedEvent( + ChatRoomJabberImpl.this, + member, + errorResultCode, + new Date(), + newMessage); + + fireMessageEvent(evt); + return; + } + + ChatRoomMessageReceivedEvent msgReceivedEvt + = new ChatRoomMessageReceivedEvent( + ChatRoomJabberImpl.this, member, new Date(), newMessage); + + fireMessageEvent(msgReceivedEvt); + } + } + + /** + * A listener that is fired anytime a MUC room changes its subject. + */ + private class SmackSubjectUpdatedListener implements SubjectUpdatedListener + { + public void subjectUpdated(String subject, String from) + { + ChatRoomPropertyChangeEvent evt + = new ChatRoomPropertyChangeEvent( + ChatRoomJabberImpl.this, + ChatRoomPropertyChangeEvent.CHAT_ROOM_SUBJECT, + oldSubject, + subject); + + firePropertyChangeEvent(evt); + + // Keeps track of the subject. + oldSubject = subject; + } + } + + /** + * Delivers the specified event to all registered property change listeners. + * + * @param evt the PropertyChangeEvent that we'd like delivered to + * all registered property change listerners. + */ + private void firePropertyChangeEvent(PropertyChangeEvent evt) + { + Iterator listeners = null; + synchronized (propertyChangeListeners) + { + listeners = new ArrayList(propertyChangeListeners).iterator(); + } + + while (listeners.hasNext()) + { + ChatRoomPropertyChangeListener listener + = (ChatRoomPropertyChangeListener) listeners.next(); + + if (evt instanceof ChatRoomPropertyChangeEvent) + { + listener.chatRoomPropertyChanged( + (ChatRoomPropertyChangeEvent) evt); + } + else if (evt instanceof ChatRoomPropertyChangeFailedEvent) + { + listener.chatRoomPropertyChangeFailed( + (ChatRoomPropertyChangeFailedEvent) evt); + } + } + } + + /** + * 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."); } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomMemberJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomMemberJabberImpl.java index ada6c6ebc..72e2dee45 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomMemberJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomMemberJabberImpl.java @@ -7,7 +7,6 @@ package net.java.sip.communicator.impl.protocol.jabber; import net.java.sip.communicator.service.protocol.*; -import org.jivesoftware.smackx.muc.*; /** * A Jabber implementation of the chat room member. diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java index 2ed30e226..a3b3a0827 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetBasicInstantMessagingJabberImpl.java @@ -8,20 +8,21 @@ import java.util.*; -import org.jivesoftware.smack.*; -import org.jivesoftware.smack.filter.*; -import org.jivesoftware.smack.packet.*; -import org.jivesoftware.smack.provider.*; -import org.jivesoftware.smack.util.*; -import org.jivesoftware.smackx.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.Message; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.service.protocol.event.MessageListener; import net.java.sip.communicator.service.protocol.jabberconstants.*; import net.java.sip.communicator.util.*; -import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*; -import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.jivesoftware.smack.util.*; +import org.jivesoftware.smackx.*; /** * A straightforward implementation of the basic instant messaging operation @@ -293,8 +294,10 @@ public void registrationStateChanged(RegistrationStateChangeEvent evt) jabberProvider.getConnection().addPacketListener( new SmackMessageListener(), - new PacketTypeFilter( - org.jivesoftware.smack.packet.Message.class)); + new AndFilter( + new PacketFilter[]{new GroupMessagePacketFilter(), + new PacketTypeFilter( + org.jivesoftware.smack.packet.Message.class)})); // run keepalive thread if(keepAliveSendTask == null && keepAliveEnabled) @@ -403,6 +406,8 @@ public void processPacket(Packet packet) return; } + // In the second condition we filter all group chat messages, + // because they are managed by the multi user chat operation set. if(sourceContact == null) { logger.debug("received a message from an unknown contact: " @@ -565,4 +570,22 @@ public void setKeepAliveEnabled(boolean keepAliveEnabled) { this.keepAliveEnabled = keepAliveEnabled; } + + private class GroupMessagePacketFilter implements PacketFilter + { + public boolean accept(Packet packet) + { + if(!(packet instanceof org.jivesoftware.smack.packet.Message)) + return false; + + org.jivesoftware.smack.packet.Message msg + = (org.jivesoftware.smack.packet.Message) packet; + + if(msg.getType().equals( + org.jivesoftware.smack.packet.Message.Type.groupchat)) + return false; + + return true; + } + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java index 3527d26a1..fc2556e5f 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java @@ -10,21 +10,20 @@ import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; -import net.java.sip.communicator.util.*; -import org.jivesoftware.smackx.muc.MultiUserChat; -import org.jivesoftware.smackx.muc.RoomInfo; -import org.jivesoftware.smack.packet.*; -import org.jivesoftware.smackx.muc.HostedRoom; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.muc.*; /** * A jabber implementation of the multi user chat operation set. * * @author Emil Ivov + * @author Yana Stamcheva */ public class OperationSetMultiUserChatJabberImpl - implements OperationSetMultiUserChat + implements OperationSetMultiUserChat { private static final Logger logger = Logger.getLogger(OperationSetMultiUserChatJabberImpl.class); @@ -59,6 +58,13 @@ public class OperationSetMultiUserChatJabberImpl */ private Hashtable chatRoomCache = new Hashtable(); + /** + * The registration listener that would get notified when the unerlying + * jabber provider gets registered. + */ + private RegistrationStateListener providerRegListener + = new RegistrationStateListener(); + /** * Instantiates the user operation set with a currently valid instance of * the jabber protocol provider. @@ -69,10 +75,8 @@ public class OperationSetMultiUserChatJabberImpl ProtocolProviderServiceJabberImpl jabberProvider) { this.jabberProvider = jabberProvider; - - /** @todo implement invitation listeners */ -// MultiUserChat.addInvitationListener(jabberProvider.getConnection() -// , this); + + jabberProvider.addRegistrationStateChangeListener(providerRegListener); } /** @@ -201,7 +205,6 @@ public ChatRoom createChatRoom(String roomName, Hashtable roomProperties) MultiUserChat muc = new MultiUserChat(getXmppConnection(), roomName); - try { muc.create(getXmppConnection().getUser()); @@ -228,11 +231,17 @@ public ChatRoom createChatRoom(String roomName, Hashtable roomProperties) private ChatRoom createLocalChatRoomInstance(MultiUserChat muc) { synchronized(chatRoomCache) - { + { ChatRoomJabberImpl chatRoom = new ChatRoomJabberImpl(muc, jabberProvider); cacheChatRoom(chatRoom); + // Add the contained in this class SmackInvitationRejectionListener + // which will dispatch all rejection events to the + // ChatRoomInvitationRejectionListener. + muc.addInvitationRejectionListener( + new SmackInvitationRejectionListener(chatRoom)); + return chatRoom; } } @@ -266,44 +275,11 @@ public ChatRoom findRoom(String roomName) { return room; } - //if not, then check whether it's on the server and if yes - create it - //locally. + //if not, we create it. else { - try - { - MultiUserChat.getRoomInfo( getXmppConnection(), roomName ); - - //if we get here then we didn't get an error while retrieving - //room info and we can assume it exists - return createLocalChatRoomInstance( - new MultiUserChat(getXmppConnection(),roomName)); - } - catch (XMPPException ex) - { - //if this is a 404 then it simply means the room doesn't exist - //on the server, so we return null and don't rethrow the - //exception - if(ex.getXMPPError() != null - && ex.getXMPPError().getCode() == 404) - { - return null; - } - logger.error( - "Failed to retrieve room info for " - + roomName - +". Error was: " - + ex.getMessage() - , ex); - - //otherwise we rethrow the exception cause it means a real error - throw new OperationFailedException( - "Failed to retrieve room info for " - + roomName - +". Error was: " - + ex.getMessage() - , OperationFailedException.GENERAL_ERROR); - } + return createLocalChatRoomInstance( + new MultiUserChat(getXmppConnection(), roomName)); } } @@ -315,11 +291,11 @@ public ChatRoom findRoom(String roomName) * a given connection. */ public List getCurrentlyJoinedChatRooms() - { + { synchronized(chatRoomCache) { List joinedRooms - = new LinkedList(this.chatRoomCache.entrySet()); + = new LinkedList(this.chatRoomCache.values()); Iterator joinedRoomsIter = joinedRooms.iterator(); @@ -382,10 +358,13 @@ public List getCurrentlyJoinedChatRooms(Contact contact) * multi user chat */ public List getExistingChatRooms() - throws OperationFailedException, OperationNotSupportedException + throws OperationFailedException, + OperationNotSupportedException { + assertSupportedAndConnected(); + List list = new LinkedList(); - + //first retrieve all conference service names available on this server Iterator serviceNames = null; try @@ -452,6 +431,10 @@ public List getExistingChatRooms() */ public boolean isMultiChatSupportedByContact(Contact contact) { + if(contact.getProtocolProvider() + .getOperationSet(OperationSetMultiUserChat.class) != null) + return true; + return false; } @@ -459,12 +442,15 @@ public boolean isMultiChatSupportedByContact(Contact contact) * Informs the sender of an invitation that we decline their invitation. * * @param invitation the connection to use for sending the rejection. - * @todo Implement this - * net.java.sip.communicator.service.protocol.OperationSetMultiUserChat - * method + * @param rejectReason the reason to reject the given invitation */ - public void rejectInvitation(ChatRoomInvitation invitation) + public void rejectInvitation(ChatRoomInvitation invitation, + String rejectReason) { + MultiUserChat.decline(jabberProvider.getConnection(), + invitation.getTargetChatRoom().getName(), + invitation.getInviter(), + rejectReason); } /** @@ -602,9 +588,223 @@ public ChatRoomJabberImpl getChatRoom(String chatRoomName) return (ChatRoomJabberImpl)this.chatRoomCache.get(chatRoomName); } + /** + * Returns the list of currently joined chat rooms. + */ public List getCurrentlyJoinedChatRooms(ChatRoomMember chatRoomMember) throws OperationFailedException, OperationNotSupportedException { return null; } + + /** + * Delivers a LocalUserChatRoomPresenceChangeEvent to all + * registered LocalUserChatRoomPresenceListeners. + * + * @param chatRoom the ChatRoom which has been joined, left, etc. + * @param eventType the type of this event; one of LOCAL_USER_JOINED, + * LOCAL_USER_LEFT, etc. + * @param reason the reason + */ + public void fireLocalUserPresenceEvent(ChatRoom chatRoom, String eventType, + String reason) + { + LocalUserChatRoomPresenceChangeEvent evt + = new LocalUserChatRoomPresenceChangeEvent( this, + chatRoom, + eventType, + reason); + + Iterator listeners = null; + synchronized (presenceListeners) + { + listeners = new ArrayList(presenceListeners).iterator(); + } + + while (listeners.hasNext()) + { + LocalUserChatRoomPresenceListener listener + = (LocalUserChatRoomPresenceListener) listeners.next(); + + listener.localUserPresenceChanged(evt); + } + } + + /** + * Delivers a ChatRoomInvitationReceivedEvent to all + * registered ChatRoomInvitationListeners. + * + * @param targetChatRoom the room that invitation refers to + * @param inviter the inviter that sent the invitation + * @param reason the reason why the inviter sent the invitation + * @param password the password to use when joining the room + */ + public void fireInvitationEvent( + ChatRoom targetChatRoom, + String inviter, + String reason, + String password) + { + ChatRoomInvitationJabberImpl invitation + = new ChatRoomInvitationJabberImpl( targetChatRoom, + inviter, + reason, + password); + + ChatRoomInvitationReceivedEvent evt + = new ChatRoomInvitationReceivedEvent(this, invitation, + new Date(System.currentTimeMillis())); + + Iterator listeners = null; + synchronized (invitationListeners) + { + listeners = new ArrayList(invitationListeners).iterator(); + } + + while (listeners.hasNext()) + { + ChatRoomInvitationListener listener + = (ChatRoomInvitationListener) listeners.next(); + + listener.invitationReceived(evt); + } + } + + /** + * Delivers a ChatRoomInvitationRejectedEvent to all + * registered ChatRoomInvitationRejectionListeners. + * + * @param sourceChatRoom the room that invitation refers to + * @param invitee the name of the invitee that rejected the invitation + * @param reason the reason of the rejection + */ + public void fireInvitationRejectedEvent(ChatRoom sourceChatRoom, + String invitee, + String reason) + { + ChatRoomInvitationRejectedEvent evt + = new ChatRoomInvitationRejectedEvent( + this, sourceChatRoom, invitee, reason, + new Date(System.currentTimeMillis())); + + Iterator listeners = null; + synchronized (invitationRejectionListeners) + { + listeners = new ArrayList(invitationRejectionListeners).iterator(); + } + + while (listeners.hasNext()) + { + ChatRoomInvitationRejectionListener listener + = (ChatRoomInvitationRejectionListener) listeners.next(); + + listener.invitationRejected(evt); + } + } + + /** + * A listener that is fired anytime an invitation to join a MUC room is + * received. + */ + private class SmackInvitationListener + implements InvitationListener + { + /** + * Called when the an invitation to join a MUC room is received.

+ * + * If the room is password-protected, the invitee will receive a + * password to use to join the room. If the room is members-only, the + * the invitee may be added to the member list. + * + * @param conn the XMPPConnection that received the invitation. + * @param room the room that invitation refers to. + * @param inviter the inviter that sent the invitation. + * (e.g. crone1@shakespeare.lit). + * @param reason the reason why the inviter sent the invitation. + * @param password the password to use when joining the room. + * @param message the message used by the inviter to send the invitation. + */ + public void invitationReceived(XMPPConnection conn, + String room, String inviter, String reason, + String password, Message message) + { + ChatRoomJabberImpl chatRoom; + try + { + chatRoom = (ChatRoomJabberImpl) findRoom(room); + + fireInvitationEvent(chatRoom, inviter, reason, password); + } + catch (OperationFailedException e) + { + logger.error("Failed to find room with name: " + room, e); + } + catch (OperationNotSupportedException e) + { + logger.error("Failed to find room with name: " + room, e); + } + } + } + + /** + * A listener that is fired anytime an invitee declines or rejects an + * invitation. + */ + private class SmackInvitationRejectionListener + implements InvitationRejectionListener + { + private ChatRoom chatRoom; + + /** + * Creates an instance of SmackInvitationRejectionListener and + * passes to it the chat room for which it will listen for rejection + * events. + * + * @param chatRoom + */ + public SmackInvitationRejectionListener(ChatRoom chatRoom) + { + this.chatRoom = chatRoom; + } + + /** + * Called when the invitee declines the invitation. + * + * @param invitee the invitee that declined the invitation. + * (e.g. hecate@shakespeare.lit). + * @param reason the reason why the invitee declined the invitation. + */ + public void invitationDeclined(String invitee, String reason) + { + fireInvitationRejectedEvent(chatRoom, invitee, reason); + } + } + + /** + * Our listener that will tell us when we're registered to jabber and the + * smack MultiUserChat is ready to accept us as a listener. + */ + private class RegistrationStateListener + implements RegistrationStateChangeListener + { + /** + * The method is called by a ProtocolProvider implementation whenver + * a change in the registration state of the corresponding provider had + * occurred. + * @param evt ProviderStatusChangeEvent the event describing the status + * change. + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + if (evt.getNewState() == RegistrationState.REGISTERED) + { + logger.debug("adding an Invitation listener to the smack muc"); + + MultiUserChat.addInvitationListener( + jabberProvider.getConnection(), + new SmackInvitationListener()); + } + } + } + } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 6c495b8c2..2dae79de9 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -7,18 +7,16 @@ package net.java.sip.communicator.impl.protocol.jabber; import java.net.*; +import java.text.*; import java.util.*; -import org.jivesoftware.smack.*; -import org.jivesoftware.smack.util.*; -import org.jivesoftware.smack.packet.*; - -import net.java.sip.communicator.impl.protocol.gibberish.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.service.protocol.jabberconstants.*; import net.java.sip.communicator.util.*; -import java.text.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.util.*; /** * An implementation of the protocol provider service over the Jabber protocol @@ -499,13 +497,13 @@ protected void initialize(String screenname, typingNotifications); //initialize the multi user chat operation set -/** @todo uncomment these when you want to use multi chat. */ -// OperationSetMultiUserChat multiUserChat = -// new OperationSetMultiUserChatJabberImpl(this); -// -// supportedOperationSets.put( -// OperationSetMultiUserChat.class.getName(), -// multiUserChat); + + OperationSetMultiUserChat multiUserChat = + new OperationSetMultiUserChatJabberImpl(this); + + supportedOperationSets.put( + OperationSetMultiUserChat.class.getName(), + multiUserChat); isInitialized = true; } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java index 7060fc4f6..e8ccb2569 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java @@ -8,13 +8,14 @@ import java.util.*; -import org.jivesoftware.smack.*; -import org.jivesoftware.smack.packet.*; -import org.jivesoftware.smackx.packet.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smackx.packet.*; + /** * This class encapsulates the Roster class. Once created, it will * register itself as a listener to the encapsulated Roster and modify it's @@ -634,9 +635,11 @@ private void initRoster() if(roster.getUnfiledEntryCount() > 0) { Iterator iter = roster.getUnfiledEntries().iterator(); + while (iter.hasNext()) { RosterEntry item = (RosterEntry) iter.next(); + ContactJabberImpl contact = findContactById(item.getUser()); @@ -656,7 +659,6 @@ private void initRoster() } } - // fill in root group Iterator iter = roster.getGroups().iterator(); while (iter.hasNext())