| /** |
| * $RCSfile$ |
| * $Revision$ |
| * $Date$ |
| * |
| * Copyright 2003-2007 Jive Software. |
| * |
| * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.jivesoftware.smackx.muc; |
| |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.WeakHashMap; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import org.jivesoftware.smack.Chat; |
| import org.jivesoftware.smack.ConnectionCreationListener; |
| import org.jivesoftware.smack.ConnectionListener; |
| import org.jivesoftware.smack.MessageListener; |
| import org.jivesoftware.smack.PacketCollector; |
| import org.jivesoftware.smack.PacketInterceptor; |
| import org.jivesoftware.smack.PacketListener; |
| import org.jivesoftware.smack.SmackConfiguration; |
| import org.jivesoftware.smack.Connection; |
| import org.jivesoftware.smack.XMPPException; |
| import org.jivesoftware.smack.filter.AndFilter; |
| import org.jivesoftware.smack.filter.FromMatchesFilter; |
| import org.jivesoftware.smack.filter.MessageTypeFilter; |
| import org.jivesoftware.smack.filter.PacketExtensionFilter; |
| import org.jivesoftware.smack.filter.PacketFilter; |
| import org.jivesoftware.smack.filter.PacketIDFilter; |
| 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.PacketExtension; |
| import org.jivesoftware.smack.packet.Presence; |
| import org.jivesoftware.smack.packet.Registration; |
| import org.jivesoftware.smackx.Form; |
| import org.jivesoftware.smackx.NodeInformationProvider; |
| import org.jivesoftware.smackx.ServiceDiscoveryManager; |
| import org.jivesoftware.smackx.packet.DiscoverInfo; |
| import org.jivesoftware.smackx.packet.DiscoverItems; |
| import org.jivesoftware.smackx.packet.MUCAdmin; |
| import org.jivesoftware.smackx.packet.MUCInitialPresence; |
| import org.jivesoftware.smackx.packet.MUCOwner; |
| import org.jivesoftware.smackx.packet.MUCUser; |
| |
| /** |
| * A MultiUserChat is a conversation that takes place among many users in a virtual |
| * room. A room could have many occupants with different affiliation and roles. |
| * Possible affiliatons are "owner", "admin", "member", and "outcast". Possible roles |
| * are "moderator", "participant", and "visitor". Each role and affiliation guarantees |
| * different privileges (e.g. Send messages to all occupants, Kick participants and visitors, |
| * Grant voice, Edit member list, etc.). |
| * |
| * @author Gaston Dombiak, Larry Kirschner |
| */ |
| public class MultiUserChat { |
| |
| private final static String discoNamespace = "http://jabber.org/protocol/muc"; |
| private final static String discoNode = "http://jabber.org/protocol/muc#rooms"; |
| |
| private static Map<Connection, List<String>> joinedRooms = |
| new WeakHashMap<Connection, List<String>>(); |
| |
| private Connection connection; |
| private String room; |
| private String subject; |
| private String nickname = null; |
| private boolean joined = false; |
| private Map<String, Presence> occupantsMap = new ConcurrentHashMap<String, Presence>(); |
| |
| private final List<InvitationRejectionListener> invitationRejectionListeners = |
| new ArrayList<InvitationRejectionListener>(); |
| private final List<SubjectUpdatedListener> subjectUpdatedListeners = |
| new ArrayList<SubjectUpdatedListener>(); |
| private final List<UserStatusListener> userStatusListeners = |
| new ArrayList<UserStatusListener>(); |
| private final List<ParticipantStatusListener> participantStatusListeners = |
| new ArrayList<ParticipantStatusListener>(); |
| |
| private PacketFilter presenceFilter; |
| private List<PacketInterceptor> presenceInterceptors = new ArrayList<PacketInterceptor>(); |
| private PacketFilter messageFilter; |
| private RoomListenerMultiplexor roomListenerMultiplexor; |
| private ConnectionDetachedPacketCollector messageCollector; |
| private List<PacketListener> connectionListeners = new ArrayList<PacketListener>(); |
| |
| static { |
| Connection.addConnectionCreationListener(new ConnectionCreationListener() { |
| public void connectionCreated(final Connection connection) { |
| // Set on every established connection that this client supports the Multi-User |
| // Chat protocol. This information will be used when another client tries to |
| // discover whether this client supports MUC or not. |
| ServiceDiscoveryManager.getInstanceFor(connection).addFeature(discoNamespace); |
| // Set the NodeInformationProvider that will provide information about the |
| // joined rooms whenever a disco request is received |
| ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider( |
| discoNode, |
| new NodeInformationProvider() { |
| public List<DiscoverItems.Item> getNodeItems() { |
| List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>(); |
| Iterator<String> rooms=MultiUserChat.getJoinedRooms(connection); |
| while (rooms.hasNext()) { |
| answer.add(new DiscoverItems.Item(rooms.next())); |
| } |
| return answer; |
| } |
| |
| public List<String> getNodeFeatures() { |
| return null; |
| } |
| |
| public List<DiscoverInfo.Identity> getNodeIdentities() { |
| return null; |
| } |
| |
| @Override |
| public List<PacketExtension> getNodePacketExtensions() { |
| return null; |
| } |
| }); |
| } |
| }); |
| } |
| |
| /** |
| * Creates a new multi user chat with the specified connection and room name. Note: no |
| * information is sent to or received from the server until you attempt to |
| * {@link #join(String) join} the chat room. On some server implementations, |
| * the room will not be created until the first person joins it.<p> |
| * |
| * Most XMPP servers use a sub-domain for the chat service (eg chat.example.com |
| * for the XMPP server example.com). You must ensure that the room address you're |
| * trying to connect to includes the proper chat sub-domain. |
| * |
| * @param connection the XMPP connection. |
| * @param room the name of the room in the form "roomName@service", where |
| * "service" is the hostname at which the multi-user chat |
| * service is running. Make sure to provide a valid JID. |
| */ |
| public MultiUserChat(Connection connection, String room) { |
| this.connection = connection; |
| this.room = room.toLowerCase(); |
| init(); |
| } |
| |
| /** |
| * Returns true if the specified user supports the Multi-User Chat protocol. |
| * |
| * @param connection the connection to use to perform the service discovery. |
| * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. |
| * @return a boolean indicating whether the specified user supports the MUC protocol. |
| */ |
| public static boolean isServiceEnabled(Connection connection, String user) { |
| try { |
| DiscoverInfo result = |
| ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(user); |
| return result.containsFeature(discoNamespace); |
| } |
| catch (XMPPException e) { |
| e.printStackTrace(); |
| return false; |
| } |
| } |
| |
| /** |
| * Returns an Iterator on the rooms where the user has joined using a given connection. |
| * The Iterator will contain Strings where each String represents a room |
| * (e.g. room@muc.jabber.org). |
| * |
| * @param connection the connection used to join the rooms. |
| * @return an Iterator on the rooms where the user has joined using a given connection. |
| */ |
| private static Iterator<String> getJoinedRooms(Connection connection) { |
| List<String> rooms = joinedRooms.get(connection); |
| if (rooms != null) { |
| return rooms.iterator(); |
| } |
| // Return an iterator on an empty collection (i.e. the user never joined a room) |
| return new ArrayList<String>().iterator(); |
| } |
| |
| /** |
| * Returns an Iterator on the rooms where the requested user has joined. The Iterator will |
| * contain Strings where each String represents a room (e.g. room@muc.jabber.org). |
| * |
| * @param connection the connection to use to perform the service discovery. |
| * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. |
| * @return an Iterator on the rooms where the requested user has joined. |
| */ |
| public static Iterator<String> getJoinedRooms(Connection connection, String user) { |
| try { |
| ArrayList<String> answer = new ArrayList<String>(); |
| // Send the disco packet to the user |
| DiscoverItems result = |
| ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(user, discoNode); |
| // Collect the entityID for each returned item |
| for (Iterator<DiscoverItems.Item> items=result.getItems(); items.hasNext();) { |
| answer.add(items.next().getEntityID()); |
| } |
| return answer.iterator(); |
| } |
| catch (XMPPException e) { |
| e.printStackTrace(); |
| // Return an iterator on an empty collection |
| return new ArrayList<String>().iterator(); |
| } |
| } |
| |
| /** |
| * Returns the discovered information of a given room without actually having to join the room. |
| * The server will provide information only for rooms that are public. |
| * |
| * @param connection the XMPP connection to use for discovering information about the room. |
| * @param room the name of the room in the form "roomName@service" of which we want to discover |
| * its information. |
| * @return the discovered information of a given room without actually having to join the room. |
| * @throws XMPPException if an error occured while trying to discover information of a room. |
| */ |
| public static RoomInfo getRoomInfo(Connection connection, String room) |
| throws XMPPException { |
| DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(room); |
| return new RoomInfo(info); |
| } |
| |
| /** |
| * Returns a collection with the XMPP addresses of the Multi-User Chat services. |
| * |
| * @param connection the XMPP connection to use for discovering Multi-User Chat services. |
| * @return a collection with the XMPP addresses of the Multi-User Chat services. |
| * @throws XMPPException if an error occured while trying to discover MUC services. |
| */ |
| public static Collection<String> getServiceNames(Connection connection) throws XMPPException { |
| final List<String> answer = new ArrayList<String>(); |
| ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); |
| DiscoverItems items = discoManager.discoverItems(connection.getServiceName()); |
| for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) { |
| DiscoverItems.Item item = it.next(); |
| try { |
| DiscoverInfo info = discoManager.discoverInfo(item.getEntityID()); |
| if (info.containsFeature("http://jabber.org/protocol/muc")) { |
| answer.add(item.getEntityID()); |
| } |
| } |
| catch (XMPPException e) { |
| // Trouble finding info in some cases. This is a workaround for |
| // discovering info on remote servers. |
| } |
| } |
| return answer; |
| } |
| |
| /** |
| * Returns a collection of HostedRooms where each HostedRoom has the XMPP address of the room |
| * and the room's name. Once discovered the rooms hosted by a chat service it is possible to |
| * discover more detailed room information or join the room. |
| * |
| * @param connection the XMPP connection to use for discovering hosted rooms by the MUC service. |
| * @param serviceName the service that is hosting the rooms to discover. |
| * @return a collection of HostedRooms. |
| * @throws XMPPException if an error occured while trying to discover the information. |
| */ |
| public static Collection<HostedRoom> getHostedRooms(Connection connection, String serviceName) |
| throws XMPPException { |
| List<HostedRoom> answer = new ArrayList<HostedRoom>(); |
| ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); |
| DiscoverItems items = discoManager.discoverItems(serviceName); |
| for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) { |
| answer.add(new HostedRoom(it.next())); |
| } |
| return answer; |
| } |
| |
| /** |
| * Returns the name of the room this MultiUserChat object represents. |
| * |
| * @return the multi user chat room name. |
| */ |
| public String getRoom() { |
| return room; |
| } |
| |
| /** |
| * Creates the room according to some default configuration, assign the requesting user |
| * as the room owner, and add the owner to the room but not allow anyone else to enter |
| * the room (effectively "locking" the room). The requesting user will join the room |
| * under the specified nickname as soon as the room has been created.<p> |
| * |
| * To create an "Instant Room", that means a room with some default configuration that is |
| * available for immediate access, the room's owner should send an empty form after creating |
| * the room. {@link #sendConfigurationForm(Form)}<p> |
| * |
| * To create a "Reserved Room", that means a room manually configured by the room creator |
| * before anyone is allowed to enter, the room's owner should complete and send a form after |
| * creating the room. Once the completed configutation form is sent to the server, the server |
| * will unlock the room. {@link #sendConfigurationForm(Form)} |
| * |
| * @param nickname the nickname to use. |
| * @throws XMPPException if the room couldn't be created for some reason |
| * (e.g. room already exists; user already joined to an existant room or |
| * 405 error if the user is not allowed to create the room) |
| */ |
| public synchronized void create(String nickname) throws XMPPException { |
| if (nickname == null || nickname.equals("")) { |
| throw new IllegalArgumentException("Nickname must not be null or blank."); |
| } |
| // If we've already joined the room, leave it before joining under a new |
| // nickname. |
| if (joined) { |
| throw new IllegalStateException("Creation failed - User already joined the room."); |
| } |
| // We create a room by sending a presence packet to room@service/nick |
| // and signal support for MUC. The owner will be automatically logged into the room. |
| Presence joinPresence = new Presence(Presence.Type.available); |
| joinPresence.setTo(room + "/" + nickname); |
| // Indicate the the client supports MUC |
| joinPresence.addExtension(new MUCInitialPresence()); |
| // Invoke presence interceptors so that extra information can be dynamically added |
| for (PacketInterceptor packetInterceptor : presenceInterceptors) { |
| packetInterceptor.interceptPacket(joinPresence); |
| } |
| |
| // Wait for a presence packet back from the server. |
| PacketFilter responseFilter = |
| new AndFilter( |
| new FromMatchesFilter(room + "/" + nickname), |
| new PacketTypeFilter(Presence.class)); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send create & join packet. |
| connection.sendPacket(joinPresence); |
| // Wait up to a certain number of seconds for a reply. |
| Presence presence = |
| (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (presence == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (presence.getError() != null) { |
| throw new XMPPException(presence.getError()); |
| } |
| // Whether the room existed before or was created, the user has joined the room |
| this.nickname = nickname; |
| joined = true; |
| userHasJoined(); |
| |
| // Look for confirmation of room creation from the server |
| MUCUser mucUser = getMUCUserExtension(presence); |
| if (mucUser != null && mucUser.getStatus() != null) { |
| if ("201".equals(mucUser.getStatus().getCode())) { |
| // Room was created and the user has joined the room |
| return; |
| } |
| } |
| // We need to leave the room since it seems that the room already existed |
| leave(); |
| throw new XMPPException("Creation failed - Missing acknowledge of room creation."); |
| } |
| |
| /** |
| * Joins the chat room using the specified nickname. If already joined |
| * using another nickname, this method will first leave the room and then |
| * re-join using the new nickname. The default timeout of Smack for a reply |
| * from the group chat server that the join succeeded will be used. After |
| * joining the room, the room will decide the amount of history to send. |
| * |
| * @param nickname the nickname to use. |
| * @throws XMPPException if an error occurs joining the room. In particular, a |
| * 401 error can occur if no password was provided and one is required; or a |
| * 403 error can occur if the user is banned; or a |
| * 404 error can occur if the room does not exist or is locked; or a |
| * 407 error can occur if user is not on the member list; or a |
| * 409 error can occur if someone is already in the group chat with the same nickname. |
| */ |
| public void join(String nickname) throws XMPPException { |
| join(nickname, null, null, SmackConfiguration.getPacketReplyTimeout()); |
| } |
| |
| /** |
| * Joins the chat room using the specified nickname and password. If already joined |
| * using another nickname, this method will first leave the room and then |
| * re-join using the new nickname. The default timeout of Smack for a reply |
| * from the group chat server that the join succeeded will be used. After |
| * joining the room, the room will decide the amount of history to send.<p> |
| * |
| * A password is required when joining password protected rooms. If the room does |
| * not require a password there is no need to provide one. |
| * |
| * @param nickname the nickname to use. |
| * @param password the password to use. |
| * @throws XMPPException if an error occurs joining the room. In particular, a |
| * 401 error can occur if no password was provided and one is required; or a |
| * 403 error can occur if the user is banned; or a |
| * 404 error can occur if the room does not exist or is locked; or a |
| * 407 error can occur if user is not on the member list; or a |
| * 409 error can occur if someone is already in the group chat with the same nickname. |
| */ |
| public void join(String nickname, String password) throws XMPPException { |
| join(nickname, password, null, SmackConfiguration.getPacketReplyTimeout()); |
| } |
| |
| /** |
| * Joins the chat room using the specified nickname and password. If already joined |
| * using another nickname, this method will first leave the room and then |
| * re-join using the new nickname.<p> |
| * |
| * To control the amount of history to receive while joining a room you will need to provide |
| * a configured DiscussionHistory object.<p> |
| * |
| * A password is required when joining password protected rooms. If the room does |
| * not require a password there is no need to provide one.<p> |
| * |
| * If the room does not already exist when the user seeks to enter it, the server will |
| * decide to create a new room or not. |
| * |
| * @param nickname the nickname to use. |
| * @param password the password to use. |
| * @param history the amount of discussion history to receive while joining a room. |
| * @param timeout the amount of time to wait for a reply from the MUC service(in milleseconds). |
| * @throws XMPPException if an error occurs joining the room. In particular, a |
| * 401 error can occur if no password was provided and one is required; or a |
| * 403 error can occur if the user is banned; or a |
| * 404 error can occur if the room does not exist or is locked; or a |
| * 407 error can occur if user is not on the member list; or a |
| * 409 error can occur if someone is already in the group chat with the same nickname. |
| */ |
| public synchronized void join( |
| String nickname, |
| String password, |
| DiscussionHistory history, |
| long timeout) |
| throws XMPPException { |
| if (nickname == null || nickname.equals("")) { |
| throw new IllegalArgumentException("Nickname must not be null or blank."); |
| } |
| // If we've already joined the room, leave it before joining under a new |
| // nickname. |
| if (joined) { |
| leave(); |
| } |
| // We join a room by sending a presence packet where the "to" |
| // field is in the form "roomName@service/nickname" |
| Presence joinPresence = new Presence(Presence.Type.available); |
| joinPresence.setTo(room + "/" + nickname); |
| |
| // Indicate the the client supports MUC |
| MUCInitialPresence mucInitialPresence = new MUCInitialPresence(); |
| if (password != null) { |
| mucInitialPresence.setPassword(password); |
| } |
| if (history != null) { |
| mucInitialPresence.setHistory(history.getMUCHistory()); |
| } |
| joinPresence.addExtension(mucInitialPresence); |
| // Invoke presence interceptors so that extra information can be dynamically added |
| for (PacketInterceptor packetInterceptor : presenceInterceptors) { |
| packetInterceptor.interceptPacket(joinPresence); |
| } |
| |
| // Wait for a presence packet back from the server. |
| PacketFilter responseFilter = |
| new AndFilter( |
| new FromMatchesFilter(room + "/" + nickname), |
| new PacketTypeFilter(Presence.class)); |
| PacketCollector response = null; |
| Presence presence; |
| try { |
| response = connection.createPacketCollector(responseFilter); |
| // Send join packet. |
| connection.sendPacket(joinPresence); |
| // Wait up to a certain number of seconds for a reply. |
| presence = (Presence) response.nextResult(timeout); |
| } |
| finally { |
| // Stop queuing results |
| if (response != null) { |
| response.cancel(); |
| } |
| } |
| |
| if (presence == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (presence.getError() != null) { |
| throw new XMPPException(presence.getError()); |
| } |
| this.nickname = nickname; |
| joined = true; |
| userHasJoined(); |
| } |
| |
| /** |
| * Returns true if currently in the multi user chat (after calling the {@link |
| * #join(String)} method). |
| * |
| * @return true if currently in the multi user chat room. |
| */ |
| public boolean isJoined() { |
| return joined; |
| } |
| |
| /** |
| * Leave the chat room. |
| */ |
| public synchronized void leave() { |
| // If not joined already, do nothing. |
| if (!joined) { |
| return; |
| } |
| // We leave a room by sending a presence packet where the "to" |
| // field is in the form "roomName@service/nickname" |
| Presence leavePresence = new Presence(Presence.Type.unavailable); |
| leavePresence.setTo(room + "/" + nickname); |
| // Invoke presence interceptors so that extra information can be dynamically added |
| for (PacketInterceptor packetInterceptor : presenceInterceptors) { |
| packetInterceptor.interceptPacket(leavePresence); |
| } |
| connection.sendPacket(leavePresence); |
| // Reset occupant information. |
| occupantsMap.clear(); |
| nickname = null; |
| joined = false; |
| userHasLeft(); |
| } |
| |
| /** |
| * Returns the room's configuration form that the room's owner can use or <tt>null</tt> if |
| * no configuration is possible. The configuration form allows to set the room's language, |
| * enable logging, specify room's type, etc.. |
| * |
| * @return the Form that contains the fields to complete together with the instrucions or |
| * <tt>null</tt> if no configuration is possible. |
| * @throws XMPPException if an error occurs asking the configuration form for the room. |
| */ |
| public Form getConfigurationForm() throws XMPPException { |
| MUCOwner iq = new MUCOwner(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.GET); |
| |
| // Filter packets looking for an answer from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Request the configuration form to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| return Form.getFormFrom(answer); |
| } |
| |
| /** |
| * Sends the completed configuration form to the server. The room will be configured |
| * with the new settings defined in the form. If the form is empty then the server |
| * will create an instant room (will use default configuration). |
| * |
| * @param form the form with the new settings. |
| * @throws XMPPException if an error occurs setting the new rooms' configuration. |
| */ |
| public void sendConfigurationForm(Form form) throws XMPPException { |
| MUCOwner iq = new MUCOwner(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.SET); |
| iq.addExtension(form.getDataFormToSend()); |
| |
| // Filter packets looking for an answer from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the completed configuration form to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| } |
| |
| /** |
| * Returns the room's registration form that an unaffiliated user, can use to become a member |
| * of the room or <tt>null</tt> if no registration is possible. Some rooms may restrict the |
| * privilege to register members and allow only room admins to add new members.<p> |
| * |
| * If the user requesting registration requirements is not allowed to register with the room |
| * (e.g. because that privilege has been restricted), the room will return a "Not Allowed" |
| * error to the user (error code 405). |
| * |
| * @return the registration Form that contains the fields to complete together with the |
| * instrucions or <tt>null</tt> if no registration is possible. |
| * @throws XMPPException if an error occurs asking the registration form for the room or a |
| * 405 error if the user is not allowed to register with the room. |
| */ |
| public Form getRegistrationForm() throws XMPPException { |
| Registration reg = new Registration(); |
| reg.setType(IQ.Type.GET); |
| reg.setTo(room); |
| |
| PacketFilter filter = |
| new AndFilter(new PacketIDFilter(reg.getPacketID()), new PacketTypeFilter(IQ.class)); |
| PacketCollector collector = connection.createPacketCollector(filter); |
| connection.sendPacket(reg); |
| IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| collector.cancel(); |
| if (result == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (result.getType() == IQ.Type.ERROR) { |
| throw new XMPPException(result.getError()); |
| } |
| return Form.getFormFrom(result); |
| } |
| |
| /** |
| * Sends the completed registration form to the server. After the user successfully submits |
| * the form, the room may queue the request for review by the room admins or may immediately |
| * add the user to the member list by changing the user's affiliation from "none" to "member.<p> |
| * |
| * If the desired room nickname is already reserved for that room, the room will return a |
| * "Conflict" error to the user (error code 409). If the room does not support registration, |
| * it will return a "Service Unavailable" error to the user (error code 503). |
| * |
| * @param form the completed registration form. |
| * @throws XMPPException if an error occurs submitting the registration form. In particular, a |
| * 409 error can occur if the desired room nickname is already reserved for that room; |
| * or a 503 error can occur if the room does not support registration. |
| */ |
| public void sendRegistrationForm(Form form) throws XMPPException { |
| Registration reg = new Registration(); |
| reg.setType(IQ.Type.SET); |
| reg.setTo(room); |
| reg.addExtension(form.getDataFormToSend()); |
| |
| PacketFilter filter = |
| new AndFilter(new PacketIDFilter(reg.getPacketID()), new PacketTypeFilter(IQ.class)); |
| PacketCollector collector = connection.createPacketCollector(filter); |
| connection.sendPacket(reg); |
| IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| collector.cancel(); |
| if (result == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (result.getType() == IQ.Type.ERROR) { |
| throw new XMPPException(result.getError()); |
| } |
| } |
| |
| /** |
| * Sends a request to the server to destroy the room. The sender of the request |
| * should be the room's owner. If the sender of the destroy request is not the room's owner |
| * then the server will answer a "Forbidden" error (403). |
| * |
| * @param reason the reason for the room destruction. |
| * @param alternateJID the JID of an alternate location. |
| * @throws XMPPException if an error occurs while trying to destroy the room. |
| * An error can occur which will be wrapped by an XMPPException -- |
| * XMPP error code 403. The error code can be used to present more |
| * appropiate error messages to end-users. |
| */ |
| public void destroy(String reason, String alternateJID) throws XMPPException { |
| MUCOwner iq = new MUCOwner(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.SET); |
| |
| // Create the reason for the room destruction |
| MUCOwner.Destroy destroy = new MUCOwner.Destroy(); |
| destroy.setReason(reason); |
| destroy.setJid(alternateJID); |
| iq.setDestroy(destroy); |
| |
| // Wait for a presence packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the room destruction request. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| // Reset occupant information. |
| occupantsMap.clear(); |
| nickname = null; |
| joined = false; |
| userHasLeft(); |
| } |
| |
| /** |
| * Invites another user to the room in which one is an occupant. The invitation |
| * will be sent to the room which in turn will forward the invitation to the invitee.<p> |
| * |
| * 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 user the user to invite to the room.(e.g. hecate@shakespeare.lit) |
| * @param reason the reason why the user is being invited. |
| */ |
| public void invite(String user, String reason) { |
| invite(new Message(), user, reason); |
| } |
| |
| /** |
| * Invites another user to the room in which one is an occupant using a given Message. The invitation |
| * will be sent to the room which in turn will forward the invitation to the invitee.<p> |
| * |
| * 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 message the message to use for sending the invitation. |
| * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit) |
| * @param reason the reason why the user is being invited. |
| */ |
| public void invite(Message message, String user, String reason) { |
| // TODO listen for 404 error code when inviter supplies a non-existent JID |
| message.setTo(room); |
| |
| // Create the MUCUser packet that will include the invitation |
| MUCUser mucUser = new MUCUser(); |
| MUCUser.Invite invite = new MUCUser.Invite(); |
| invite.setTo(user); |
| invite.setReason(reason); |
| mucUser.setInvite(invite); |
| // Add the MUCUser packet that includes the invitation to the message |
| message.addExtension(mucUser); |
| |
| connection.sendPacket(message); |
| } |
| |
| /** |
| * Informs the sender of an invitation that the invitee declines the invitation. The rejection |
| * will be sent to the room which in turn will forward the rejection to the inviter. |
| * |
| * @param conn the connection to use for sending the rejection. |
| * @param room the room that sent the original invitation. |
| * @param inviter the inviter of the declined invitation. |
| * @param reason the reason why the invitee is declining the invitation. |
| */ |
| public static void decline(Connection conn, String room, String inviter, String reason) { |
| Message message = new Message(room); |
| |
| // Create the MUCUser packet that will include the rejection |
| MUCUser mucUser = new MUCUser(); |
| MUCUser.Decline decline = new MUCUser.Decline(); |
| decline.setTo(inviter); |
| decline.setReason(reason); |
| mucUser.setDecline(decline); |
| // Add the MUCUser packet that includes the rejection |
| message.addExtension(mucUser); |
| |
| conn.sendPacket(message); |
| } |
| |
| /** |
| * Adds a listener to invitation notifications. The listener will be fired anytime |
| * an invitation is received. |
| * |
| * @param conn the connection where the listener will be applied. |
| * @param listener an invitation listener. |
| */ |
| public static void addInvitationListener(Connection conn, InvitationListener listener) { |
| InvitationsMonitor.getInvitationsMonitor(conn).addInvitationListener(listener); |
| } |
| |
| /** |
| * Removes a listener to invitation notifications. The listener will be fired anytime |
| * an invitation is received. |
| * |
| * @param conn the connection where the listener was applied. |
| * @param listener an invitation listener. |
| */ |
| public static void removeInvitationListener(Connection conn, InvitationListener listener) { |
| InvitationsMonitor.getInvitationsMonitor(conn).removeInvitationListener(listener); |
| } |
| |
| /** |
| * Adds a listener to invitation rejections notifications. The listener will be fired anytime |
| * an invitation is declined. |
| * |
| * @param listener an invitation rejection listener. |
| */ |
| public void addInvitationRejectionListener(InvitationRejectionListener listener) { |
| synchronized (invitationRejectionListeners) { |
| if (!invitationRejectionListeners.contains(listener)) { |
| invitationRejectionListeners.add(listener); |
| } |
| } |
| } |
| |
| /** |
| * Removes a listener from invitation rejections notifications. The listener will be fired |
| * anytime an invitation is declined. |
| * |
| * @param listener an invitation rejection listener. |
| */ |
| public void removeInvitationRejectionListener(InvitationRejectionListener listener) { |
| synchronized (invitationRejectionListeners) { |
| invitationRejectionListeners.remove(listener); |
| } |
| } |
| |
| /** |
| * Fires invitation rejection listeners. |
| * |
| * @param invitee the user being invited. |
| * @param reason the reason for the rejection |
| */ |
| private void fireInvitationRejectionListeners(String invitee, String reason) { |
| InvitationRejectionListener[] listeners; |
| synchronized (invitationRejectionListeners) { |
| listeners = new InvitationRejectionListener[invitationRejectionListeners.size()]; |
| invitationRejectionListeners.toArray(listeners); |
| } |
| for (InvitationRejectionListener listener : listeners) { |
| listener.invitationDeclined(invitee, reason); |
| } |
| } |
| |
| /** |
| * Adds a listener to subject change notifications. The listener will be fired anytime |
| * the room's subject changes. |
| * |
| * @param listener a subject updated listener. |
| */ |
| public void addSubjectUpdatedListener(SubjectUpdatedListener listener) { |
| synchronized (subjectUpdatedListeners) { |
| if (!subjectUpdatedListeners.contains(listener)) { |
| subjectUpdatedListeners.add(listener); |
| } |
| } |
| } |
| |
| /** |
| * Removes a listener from subject change notifications. The listener will be fired |
| * anytime the room's subject changes. |
| * |
| * @param listener a subject updated listener. |
| */ |
| public void removeSubjectUpdatedListener(SubjectUpdatedListener listener) { |
| synchronized (subjectUpdatedListeners) { |
| subjectUpdatedListeners.remove(listener); |
| } |
| } |
| |
| /** |
| * Fires subject updated listeners. |
| */ |
| private void fireSubjectUpdatedListeners(String subject, String from) { |
| SubjectUpdatedListener[] listeners; |
| synchronized (subjectUpdatedListeners) { |
| listeners = new SubjectUpdatedListener[subjectUpdatedListeners.size()]; |
| subjectUpdatedListeners.toArray(listeners); |
| } |
| for (SubjectUpdatedListener listener : listeners) { |
| listener.subjectUpdated(subject, from); |
| } |
| } |
| |
| /** |
| * Adds a new {@link PacketInterceptor} that will be invoked every time a new presence |
| * is going to be sent by this MultiUserChat to the server. Packet interceptors may |
| * add new extensions to the presence that is going to be sent to the MUC service. |
| * |
| * @param presenceInterceptor the new packet interceptor that will intercept presence packets. |
| */ |
| public void addPresenceInterceptor(PacketInterceptor presenceInterceptor) { |
| presenceInterceptors.add(presenceInterceptor); |
| } |
| |
| /** |
| * Removes a {@link PacketInterceptor} that was being invoked every time a new presence |
| * was being sent by this MultiUserChat to the server. Packet interceptors may |
| * add new extensions to the presence that is going to be sent to the MUC service. |
| * |
| * @param presenceInterceptor the packet interceptor to remove. |
| */ |
| public void removePresenceInterceptor(PacketInterceptor presenceInterceptor) { |
| presenceInterceptors.remove(presenceInterceptor); |
| } |
| |
| /** |
| * Returns the last known room's subject or <tt>null</tt> if the user hasn't joined the room |
| * or the room does not have a subject yet. In case the room has a subject, as soon as the |
| * user joins the room a message with the current room's subject will be received.<p> |
| * |
| * To be notified every time the room's subject change you should add a listener |
| * to this room. {@link #addSubjectUpdatedListener(SubjectUpdatedListener)}<p> |
| * |
| * To change the room's subject use {@link #changeSubject(String)}. |
| * |
| * @return the room's subject or <tt>null</tt> if the user hasn't joined the room or the |
| * room does not have a subject yet. |
| */ |
| public String getSubject() { |
| return subject; |
| } |
| |
| /** |
| * Returns the reserved room nickname for the user in the room. A user may have a reserved |
| * nickname, for example through explicit room registration or database integration. In such |
| * cases it may be desirable for the user to discover the reserved nickname before attempting |
| * to enter the room. |
| * |
| * @return the reserved room nickname or <tt>null</tt> if none. |
| */ |
| public String getReservedNickname() { |
| try { |
| DiscoverInfo result = |
| ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo( |
| room, |
| "x-roomuser-item"); |
| // Look for an Identity that holds the reserved nickname and return its name |
| for (Iterator<DiscoverInfo.Identity> identities = result.getIdentities(); |
| identities.hasNext();) { |
| DiscoverInfo.Identity identity = identities.next(); |
| return identity.getName(); |
| } |
| // If no Identity was found then the user does not have a reserved room nickname |
| return null; |
| } |
| catch (XMPPException e) { |
| e.printStackTrace(); |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the nickname that was used to join the room, or <tt>null</tt> if not |
| * currently joined. |
| * |
| * @return the nickname currently being used. |
| */ |
| public String getNickname() { |
| return nickname; |
| } |
| |
| /** |
| * Changes the occupant's nickname to a new nickname within the room. Each room occupant |
| * will receive two presence packets. One of type "unavailable" for the old nickname and one |
| * indicating availability for the new nickname. The unavailable presence will contain the new |
| * nickname and an appropriate status code (namely 303) as extended presence information. The |
| * status code 303 indicates that the occupant is changing his/her nickname. |
| * |
| * @param nickname the new nickname within the room. |
| * @throws XMPPException if the new nickname is already in use by another occupant. |
| */ |
| public void changeNickname(String nickname) throws XMPPException { |
| if (nickname == null || nickname.equals("")) { |
| throw new IllegalArgumentException("Nickname must not be null or blank."); |
| } |
| // Check that we already have joined the room before attempting to change the |
| // nickname. |
| if (!joined) { |
| throw new IllegalStateException("Must be logged into the room to change nickname."); |
| } |
| // We change the nickname by sending a presence packet where the "to" |
| // field is in the form "roomName@service/nickname" |
| // We don't have to signal the MUC support again |
| Presence joinPresence = new Presence(Presence.Type.available); |
| joinPresence.setTo(room + "/" + nickname); |
| // Invoke presence interceptors so that extra information can be dynamically added |
| for (PacketInterceptor packetInterceptor : presenceInterceptors) { |
| packetInterceptor.interceptPacket(joinPresence); |
| } |
| |
| // Wait for a presence packet back from the server. |
| PacketFilter responseFilter = |
| new AndFilter( |
| new FromMatchesFilter(room + "/" + nickname), |
| new PacketTypeFilter(Presence.class)); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send join packet. |
| connection.sendPacket(joinPresence); |
| // Wait up to a certain number of seconds for a reply. |
| Presence presence = |
| (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (presence == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (presence.getError() != null) { |
| throw new XMPPException(presence.getError()); |
| } |
| this.nickname = nickname; |
| } |
| |
| /** |
| * Changes the occupant's availability status within the room. The presence type |
| * will remain available but with a new status that describes the presence update and |
| * a new presence mode (e.g. Extended away). |
| * |
| * @param status a text message describing the presence update. |
| * @param mode the mode type for the presence update. |
| */ |
| public void changeAvailabilityStatus(String status, Presence.Mode mode) { |
| if (nickname == null || nickname.equals("")) { |
| throw new IllegalArgumentException("Nickname must not be null or blank."); |
| } |
| // Check that we already have joined the room before attempting to change the |
| // availability status. |
| if (!joined) { |
| throw new IllegalStateException( |
| "Must be logged into the room to change the " + "availability status."); |
| } |
| // We change the availability status by sending a presence packet to the room with the |
| // new presence status and mode |
| Presence joinPresence = new Presence(Presence.Type.available); |
| joinPresence.setStatus(status); |
| joinPresence.setMode(mode); |
| joinPresence.setTo(room + "/" + nickname); |
| // Invoke presence interceptors so that extra information can be dynamically added |
| for (PacketInterceptor packetInterceptor : presenceInterceptors) { |
| packetInterceptor.interceptPacket(joinPresence); |
| } |
| |
| // Send join packet. |
| connection.sendPacket(joinPresence); |
| } |
| |
| /** |
| * Kicks a visitor or participant from the room. The kicked occupant will receive a presence |
| * of type "unavailable" including a status code 307 and optionally along with the reason |
| * (if provided) and the bare JID of the user who initiated the kick. After the occupant |
| * was kicked from the room, the rest of the occupants will receive a presence of type |
| * "unavailable". The presence will include a status code 307 which means that the occupant |
| * was kicked from the room. |
| * |
| * @param nickname the nickname of the participant or visitor to kick from the room |
| * (e.g. "john"). |
| * @param reason the reason why the participant or visitor is being kicked from the room. |
| * @throws XMPPException if an error occurs kicking the occupant. In particular, a |
| * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" |
| * was intended to be kicked (i.e. Not Allowed error); or a |
| * 403 error can occur if the occupant that intended to kick another occupant does |
| * not have kicking privileges (i.e. Forbidden error); or a |
| * 400 error can occur if the provided nickname is not present in the room. |
| */ |
| public void kickParticipant(String nickname, String reason) throws XMPPException { |
| changeRole(nickname, "none", reason); |
| } |
| |
| /** |
| * Grants voice to visitors in the room. In a moderated room, a moderator may want to manage |
| * who does and does not have "voice" in the room. To have voice means that a room occupant |
| * is able to send messages to the room occupants. |
| * |
| * @param nicknames the nicknames of the visitors to grant voice in the room (e.g. "john"). |
| * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a |
| * 403 error can occur if the occupant that intended to grant voice is not |
| * a moderator in this room (i.e. Forbidden error); or a |
| * 400 error can occur if the provided nickname is not present in the room. |
| */ |
| public void grantVoice(Collection<String> nicknames) throws XMPPException { |
| changeRole(nicknames, "participant"); |
| } |
| |
| /** |
| * Grants voice to a visitor in the room. In a moderated room, a moderator may want to manage |
| * who does and does not have "voice" in the room. To have voice means that a room occupant |
| * is able to send messages to the room occupants. |
| * |
| * @param nickname the nickname of the visitor to grant voice in the room (e.g. "john"). |
| * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a |
| * 403 error can occur if the occupant that intended to grant voice is not |
| * a moderator in this room (i.e. Forbidden error); or a |
| * 400 error can occur if the provided nickname is not present in the room. |
| */ |
| public void grantVoice(String nickname) throws XMPPException { |
| changeRole(nickname, "participant", null); |
| } |
| |
| /** |
| * Revokes voice from participants in the room. In a moderated room, a moderator may want to |
| * revoke an occupant's privileges to speak. To have voice means that a room occupant |
| * is able to send messages to the room occupants. |
| * |
| * @param nicknames the nicknames of the participants to revoke voice (e.g. "john"). |
| * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a |
| * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" |
| * was tried to revoke his voice (i.e. Not Allowed error); or a |
| * 400 error can occur if the provided nickname is not present in the room. |
| */ |
| public void revokeVoice(Collection<String> nicknames) throws XMPPException { |
| changeRole(nicknames, "visitor"); |
| } |
| |
| /** |
| * Revokes voice from a participant in the room. In a moderated room, a moderator may want to |
| * revoke an occupant's privileges to speak. To have voice means that a room occupant |
| * is able to send messages to the room occupants. |
| * |
| * @param nickname the nickname of the participant to revoke voice (e.g. "john"). |
| * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a |
| * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" |
| * was tried to revoke his voice (i.e. Not Allowed error); or a |
| * 400 error can occur if the provided nickname is not present in the room. |
| */ |
| public void revokeVoice(String nickname) throws XMPPException { |
| changeRole(nickname, "visitor", null); |
| } |
| |
| /** |
| * Bans users from the room. An admin or owner of the room can ban users from a room. This |
| * means that the banned user will no longer be able to join the room unless the ban has been |
| * removed. If the banned user was present in the room then he/she will be removed from the |
| * room and notified that he/she was banned along with the reason (if provided) and the bare |
| * XMPP user ID of the user who initiated the ban. |
| * |
| * @param jids the bare XMPP user IDs of the users to ban. |
| * @throws XMPPException if an error occurs banning a user. In particular, a |
| * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" |
| * was tried to be banned (i.e. Not Allowed error). |
| */ |
| public void banUsers(Collection<String> jids) throws XMPPException { |
| changeAffiliationByAdmin(jids, "outcast"); |
| } |
| |
| /** |
| * Bans a user from the room. An admin or owner of the room can ban users from a room. This |
| * means that the banned user will no longer be able to join the room unless the ban has been |
| * removed. If the banned user was present in the room then he/she will be removed from the |
| * room and notified that he/she was banned along with the reason (if provided) and the bare |
| * XMPP user ID of the user who initiated the ban. |
| * |
| * @param jid the bare XMPP user ID of the user to ban (e.g. "user@host.org"). |
| * @param reason the optional reason why the user was banned. |
| * @throws XMPPException if an error occurs banning a user. In particular, a |
| * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" |
| * was tried to be banned (i.e. Not Allowed error). |
| */ |
| public void banUser(String jid, String reason) throws XMPPException { |
| changeAffiliationByAdmin(jid, "outcast", reason); |
| } |
| |
| /** |
| * Grants membership to other users. Only administrators are able to grant membership. A user |
| * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room |
| * that a user cannot enter without being on the member list). |
| * |
| * @param jids the XMPP user IDs of the users to grant membership. |
| * @throws XMPPException if an error occurs granting membership to a user. |
| */ |
| public void grantMembership(Collection<String> jids) throws XMPPException { |
| changeAffiliationByAdmin(jids, "member"); |
| } |
| |
| /** |
| * Grants membership to a user. Only administrators are able to grant membership. A user |
| * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room |
| * that a user cannot enter without being on the member list). |
| * |
| * @param jid the XMPP user ID of the user to grant membership (e.g. "user@host.org"). |
| * @throws XMPPException if an error occurs granting membership to a user. |
| */ |
| public void grantMembership(String jid) throws XMPPException { |
| changeAffiliationByAdmin(jid, "member", null); |
| } |
| |
| /** |
| * Revokes users' membership. Only administrators are able to revoke membership. A user |
| * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room |
| * that a user cannot enter without being on the member list). If the user is in the room and |
| * the room is of type members-only then the user will be removed from the room. |
| * |
| * @param jids the bare XMPP user IDs of the users to revoke membership. |
| * @throws XMPPException if an error occurs revoking membership to a user. |
| */ |
| public void revokeMembership(Collection<String> jids) throws XMPPException { |
| changeAffiliationByAdmin(jids, "none"); |
| } |
| |
| /** |
| * Revokes a user's membership. Only administrators are able to revoke membership. A user |
| * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room |
| * that a user cannot enter without being on the member list). If the user is in the room and |
| * the room is of type members-only then the user will be removed from the room. |
| * |
| * @param jid the bare XMPP user ID of the user to revoke membership (e.g. "user@host.org"). |
| * @throws XMPPException if an error occurs revoking membership to a user. |
| */ |
| public void revokeMembership(String jid) throws XMPPException { |
| changeAffiliationByAdmin(jid, "none", null); |
| } |
| |
| /** |
| * Grants moderator privileges to participants or visitors. Room administrators may grant |
| * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite |
| * other users, modify room's subject plus all the partcipants privileges. |
| * |
| * @param nicknames the nicknames of the occupants to grant moderator privileges. |
| * @throws XMPPException if an error occurs granting moderator privileges to a user. |
| */ |
| public void grantModerator(Collection<String> nicknames) throws XMPPException { |
| changeRole(nicknames, "moderator"); |
| } |
| |
| /** |
| * Grants moderator privileges to a participant or visitor. Room administrators may grant |
| * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite |
| * other users, modify room's subject plus all the partcipants privileges. |
| * |
| * @param nickname the nickname of the occupant to grant moderator privileges. |
| * @throws XMPPException if an error occurs granting moderator privileges to a user. |
| */ |
| public void grantModerator(String nickname) throws XMPPException { |
| changeRole(nickname, "moderator", null); |
| } |
| |
| /** |
| * Revokes moderator privileges from other users. The occupant that loses moderator |
| * privileges will become a participant. Room administrators may revoke moderator privileges |
| * only to occupants whose affiliation is member or none. This means that an administrator is |
| * not allowed to revoke moderator privileges from other room administrators or owners. |
| * |
| * @param nicknames the nicknames of the occupants to revoke moderator privileges. |
| * @throws XMPPException if an error occurs revoking moderator privileges from a user. |
| */ |
| public void revokeModerator(Collection<String> nicknames) throws XMPPException { |
| changeRole(nicknames, "participant"); |
| } |
| |
| /** |
| * Revokes moderator privileges from another user. The occupant that loses moderator |
| * privileges will become a participant. Room administrators may revoke moderator privileges |
| * only to occupants whose affiliation is member or none. This means that an administrator is |
| * not allowed to revoke moderator privileges from other room administrators or owners. |
| * |
| * @param nickname the nickname of the occupant to revoke moderator privileges. |
| * @throws XMPPException if an error occurs revoking moderator privileges from a user. |
| */ |
| public void revokeModerator(String nickname) throws XMPPException { |
| changeRole(nickname, "participant", null); |
| } |
| |
| /** |
| * Grants ownership privileges to other users. Room owners may grant ownership privileges. |
| * Some room implementations will not allow to grant ownership privileges to other users. |
| * An owner is allowed to change defining room features as well as perform all administrative |
| * functions. |
| * |
| * @param jids the collection of bare XMPP user IDs of the users to grant ownership. |
| * @throws XMPPException if an error occurs granting ownership privileges to a user. |
| */ |
| public void grantOwnership(Collection<String> jids) throws XMPPException { |
| changeAffiliationByAdmin(jids, "owner"); |
| } |
| |
| /** |
| * Grants ownership privileges to another user. Room owners may grant ownership privileges. |
| * Some room implementations will not allow to grant ownership privileges to other users. |
| * An owner is allowed to change defining room features as well as perform all administrative |
| * functions. |
| * |
| * @param jid the bare XMPP user ID of the user to grant ownership (e.g. "user@host.org"). |
| * @throws XMPPException if an error occurs granting ownership privileges to a user. |
| */ |
| public void grantOwnership(String jid) throws XMPPException { |
| changeAffiliationByAdmin(jid, "owner", null); |
| } |
| |
| /** |
| * Revokes ownership privileges from other users. The occupant that loses ownership |
| * privileges will become an administrator. Room owners may revoke ownership privileges. |
| * Some room implementations will not allow to grant ownership privileges to other users. |
| * |
| * @param jids the bare XMPP user IDs of the users to revoke ownership. |
| * @throws XMPPException if an error occurs revoking ownership privileges from a user. |
| */ |
| public void revokeOwnership(Collection<String> jids) throws XMPPException { |
| changeAffiliationByAdmin(jids, "admin"); |
| } |
| |
| /** |
| * Revokes ownership privileges from another user. The occupant that loses ownership |
| * privileges will become an administrator. Room owners may revoke ownership privileges. |
| * Some room implementations will not allow to grant ownership privileges to other users. |
| * |
| * @param jid the bare XMPP user ID of the user to revoke ownership (e.g. "user@host.org"). |
| * @throws XMPPException if an error occurs revoking ownership privileges from a user. |
| */ |
| public void revokeOwnership(String jid) throws XMPPException { |
| changeAffiliationByAdmin(jid, "admin", null); |
| } |
| |
| /** |
| * Grants administrator privileges to other users. Room owners may grant administrator |
| * privileges to a member or unaffiliated user. An administrator is allowed to perform |
| * administrative functions such as banning users and edit moderator list. |
| * |
| * @param jids the bare XMPP user IDs of the users to grant administrator privileges. |
| * @throws XMPPException if an error occurs granting administrator privileges to a user. |
| */ |
| public void grantAdmin(Collection<String> jids) throws XMPPException { |
| changeAffiliationByOwner(jids, "admin"); |
| } |
| |
| /** |
| * Grants administrator privileges to another user. Room owners may grant administrator |
| * privileges to a member or unaffiliated user. An administrator is allowed to perform |
| * administrative functions such as banning users and edit moderator list. |
| * |
| * @param jid the bare XMPP user ID of the user to grant administrator privileges |
| * (e.g. "user@host.org"). |
| * @throws XMPPException if an error occurs granting administrator privileges to a user. |
| */ |
| public void grantAdmin(String jid) throws XMPPException { |
| changeAffiliationByOwner(jid, "admin"); |
| } |
| |
| /** |
| * Revokes administrator privileges from users. The occupant that loses administrator |
| * privileges will become a member. Room owners may revoke administrator privileges from |
| * a member or unaffiliated user. |
| * |
| * @param jids the bare XMPP user IDs of the user to revoke administrator privileges. |
| * @throws XMPPException if an error occurs revoking administrator privileges from a user. |
| */ |
| public void revokeAdmin(Collection<String> jids) throws XMPPException { |
| changeAffiliationByOwner(jids, "member"); |
| } |
| |
| /** |
| * Revokes administrator privileges from a user. The occupant that loses administrator |
| * privileges will become a member. Room owners may revoke administrator privileges from |
| * a member or unaffiliated user. |
| * |
| * @param jid the bare XMPP user ID of the user to revoke administrator privileges |
| * (e.g. "user@host.org"). |
| * @throws XMPPException if an error occurs revoking administrator privileges from a user. |
| */ |
| public void revokeAdmin(String jid) throws XMPPException { |
| changeAffiliationByOwner(jid, "member"); |
| } |
| |
| private void changeAffiliationByOwner(String jid, String affiliation) throws XMPPException { |
| MUCOwner iq = new MUCOwner(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.SET); |
| // Set the new affiliation. |
| MUCOwner.Item item = new MUCOwner.Item(affiliation); |
| item.setJid(jid); |
| iq.addItem(item); |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the change request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| } |
| |
| private void changeAffiliationByOwner(Collection<String> jids, String affiliation) |
| throws XMPPException { |
| MUCOwner iq = new MUCOwner(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.SET); |
| for (String jid : jids) { |
| // Set the new affiliation. |
| MUCOwner.Item item = new MUCOwner.Item(affiliation); |
| item.setJid(jid); |
| iq.addItem(item); |
| } |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the change request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| } |
| |
| /** |
| * Tries to change the affiliation with an 'muc#admin' namespace |
| * |
| * @param jid |
| * @param affiliation |
| * @param reason the reason for the affiliation change (optional) |
| * @throws XMPPException |
| */ |
| private void changeAffiliationByAdmin(String jid, String affiliation, String reason) |
| throws XMPPException { |
| MUCAdmin iq = new MUCAdmin(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.SET); |
| // Set the new affiliation. |
| MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); |
| item.setJid(jid); |
| if(reason != null) |
| item.setReason(reason); |
| iq.addItem(item); |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the change request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| } |
| |
| private void changeAffiliationByAdmin(Collection<String> jids, String affiliation) |
| throws XMPPException { |
| MUCAdmin iq = new MUCAdmin(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.SET); |
| for (String jid : jids) { |
| // Set the new affiliation. |
| MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); |
| item.setJid(jid); |
| iq.addItem(item); |
| } |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the change request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| } |
| |
| private void changeRole(String nickname, String role, String reason) throws XMPPException { |
| MUCAdmin iq = new MUCAdmin(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.SET); |
| // Set the new role. |
| MUCAdmin.Item item = new MUCAdmin.Item(null, role); |
| item.setNick(nickname); |
| item.setReason(reason); |
| iq.addItem(item); |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the change request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| } |
| |
| private void changeRole(Collection<String> nicknames, String role) throws XMPPException { |
| MUCAdmin iq = new MUCAdmin(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.SET); |
| for (String nickname : nicknames) { |
| // Set the new role. |
| MUCAdmin.Item item = new MUCAdmin.Item(null, role); |
| item.setNick(nickname); |
| iq.addItem(item); |
| } |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the change request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| } |
| |
| /** |
| * Returns the number of occupants in the group chat.<p> |
| * |
| * Note: this value will only be accurate after joining the group chat, and |
| * may fluctuate over time. If you query this value directly after joining the |
| * group chat it may not be accurate, as it takes a certain amount of time for |
| * the server to send all presence packets to this client. |
| * |
| * @return the number of occupants in the group chat. |
| */ |
| public int getOccupantsCount() { |
| return occupantsMap.size(); |
| } |
| |
| /** |
| * Returns an Iterator (of Strings) for the list of fully qualified occupants |
| * in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser". |
| * Typically, a client would only display the nickname of the occupant. To |
| * get the nickname from the fully qualified name, use the |
| * {@link org.jivesoftware.smack.util.StringUtils#parseResource(String)} method. |
| * Note: this value will only be accurate after joining the group chat, and may |
| * fluctuate over time. |
| * |
| * @return an Iterator for the occupants in the group chat. |
| */ |
| public Iterator<String> getOccupants() { |
| return Collections.unmodifiableList(new ArrayList<String>(occupantsMap.keySet())) |
| .iterator(); |
| } |
| |
| /** |
| * Returns the presence info for a particular user, or <tt>null</tt> if the user |
| * is not in the room.<p> |
| * |
| * @param user the room occupant to search for his presence. The format of user must |
| * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch). |
| * @return the occupant's current presence, or <tt>null</tt> if the user is unavailable |
| * or if no presence information is available. |
| */ |
| public Presence getOccupantPresence(String user) { |
| return occupantsMap.get(user); |
| } |
| |
| /** |
| * Returns the Occupant information for a particular occupant, or <tt>null</tt> if the |
| * user is not in the room. The Occupant object may include information such as full |
| * JID of the user as well as the role and affiliation of the user in the room.<p> |
| * |
| * @param user the room occupant to search for his presence. The format of user must |
| * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch). |
| * @return the Occupant or <tt>null</tt> if the user is unavailable (i.e. not in the room). |
| */ |
| public Occupant getOccupant(String user) { |
| Presence presence = occupantsMap.get(user); |
| if (presence != null) { |
| return new Occupant(presence); |
| } |
| return null; |
| } |
| |
| /** |
| * Adds a packet listener that will be notified of any new Presence packets |
| * sent to the group chat. Using a listener is a suitable way to know when the list |
| * of occupants should be re-loaded due to any changes. |
| * |
| * @param listener a packet listener that will be notified of any presence packets |
| * sent to the group chat. |
| */ |
| public void addParticipantListener(PacketListener listener) { |
| connection.addPacketListener(listener, presenceFilter); |
| connectionListeners.add(listener); |
| } |
| |
| /** |
| * Remoces a packet listener that was being notified of any new Presence packets |
| * sent to the group chat. |
| * |
| * @param listener a packet listener that was being notified of any presence packets |
| * sent to the group chat. |
| */ |
| public void removeParticipantListener(PacketListener listener) { |
| connection.removePacketListener(listener); |
| connectionListeners.remove(listener); |
| } |
| |
| /** |
| * Returns a collection of <code>Affiliate</code> with the room owners. |
| * |
| * @return a collection of <code>Affiliate</code> with the room owners. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| public Collection<Affiliate> getOwners() throws XMPPException { |
| return getAffiliatesByAdmin("owner"); |
| } |
| |
| /** |
| * Returns a collection of <code>Affiliate</code> with the room administrators. |
| * |
| * @return a collection of <code>Affiliate</code> with the room administrators. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| public Collection<Affiliate> getAdmins() throws XMPPException { |
| return getAffiliatesByOwner("admin"); |
| } |
| |
| /** |
| * Returns a collection of <code>Affiliate</code> with the room members. |
| * |
| * @return a collection of <code>Affiliate</code> with the room members. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| public Collection<Affiliate> getMembers() throws XMPPException { |
| return getAffiliatesByAdmin("member"); |
| } |
| |
| /** |
| * Returns a collection of <code>Affiliate</code> with the room outcasts. |
| * |
| * @return a collection of <code>Affiliate</code> with the room outcasts. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| public Collection<Affiliate> getOutcasts() throws XMPPException { |
| return getAffiliatesByAdmin("outcast"); |
| } |
| |
| /** |
| * Returns a collection of <code>Affiliate</code> that have the specified room affiliation |
| * sending a request in the owner namespace. |
| * |
| * @param affiliation the affiliation of the users in the room. |
| * @return a collection of <code>Affiliate</code> that have the specified room affiliation. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| private Collection<Affiliate> getAffiliatesByOwner(String affiliation) throws XMPPException { |
| MUCOwner iq = new MUCOwner(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.GET); |
| // Set the specified affiliation. This may request the list of owners/admins/members/outcasts. |
| MUCOwner.Item item = new MUCOwner.Item(affiliation); |
| iq.addItem(item); |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| MUCOwner answer = (MUCOwner) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| // Get the list of affiliates from the server's answer |
| List<Affiliate> affiliates = new ArrayList<Affiliate>(); |
| for (Iterator<MUCOwner.Item> it = answer.getItems(); it.hasNext();) { |
| affiliates.add(new Affiliate(it.next())); |
| } |
| return affiliates; |
| } |
| |
| /** |
| * Returns a collection of <code>Affiliate</code> that have the specified room affiliation |
| * sending a request in the admin namespace. |
| * |
| * @param affiliation the affiliation of the users in the room. |
| * @return a collection of <code>Affiliate</code> that have the specified room affiliation. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| private Collection<Affiliate> getAffiliatesByAdmin(String affiliation) throws XMPPException { |
| MUCAdmin iq = new MUCAdmin(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.GET); |
| // Set the specified affiliation. This may request the list of owners/admins/members/outcasts. |
| MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); |
| iq.addItem(item); |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| // Get the list of affiliates from the server's answer |
| List<Affiliate> affiliates = new ArrayList<Affiliate>(); |
| for (Iterator<MUCAdmin.Item> it = answer.getItems(); it.hasNext();) { |
| affiliates.add(new Affiliate(it.next())); |
| } |
| return affiliates; |
| } |
| |
| /** |
| * Returns a collection of <code>Occupant</code> with the room moderators. |
| * |
| * @return a collection of <code>Occupant</code> with the room moderators. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| public Collection<Occupant> getModerators() throws XMPPException { |
| return getOccupants("moderator"); |
| } |
| |
| /** |
| * Returns a collection of <code>Occupant</code> with the room participants. |
| * |
| * @return a collection of <code>Occupant</code> with the room participants. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| public Collection<Occupant> getParticipants() throws XMPPException { |
| return getOccupants("participant"); |
| } |
| |
| /** |
| * Returns a collection of <code>Occupant</code> that have the specified room role. |
| * |
| * @param role the role of the occupant in the room. |
| * @return a collection of <code>Occupant</code> that have the specified room role. |
| * @throws XMPPException if an error occured while performing the request to the server or you |
| * don't have enough privileges to get this information. |
| */ |
| private Collection<Occupant> getOccupants(String role) throws XMPPException { |
| MUCAdmin iq = new MUCAdmin(); |
| iq.setTo(room); |
| iq.setType(IQ.Type.GET); |
| // Set the specified role. This may request the list of moderators/participants. |
| MUCAdmin.Item item = new MUCAdmin.Item(null, role); |
| iq.addItem(item); |
| |
| // Wait for a response packet back from the server. |
| PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send the request to the server. |
| connection.sendPacket(iq); |
| // Wait up to a certain number of seconds for a reply. |
| MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| // Get the list of participants from the server's answer |
| List<Occupant> participants = new ArrayList<Occupant>(); |
| for (Iterator<MUCAdmin.Item> it = answer.getItems(); it.hasNext();) { |
| participants.add(new Occupant(it.next())); |
| } |
| return participants; |
| } |
| |
| /** |
| * Sends a message to the chat room. |
| * |
| * @param text the text of the message to send. |
| * @throws XMPPException if sending the message fails. |
| */ |
| public void sendMessage(String text) throws XMPPException { |
| Message message = new Message(room, Message.Type.groupchat); |
| message.setBody(text); |
| connection.sendPacket(message); |
| } |
| |
| /** |
| * Returns a new Chat for sending private messages to a given room occupant. |
| * The Chat's occupant address is the room's JID (i.e. roomName@service/nick). The server |
| * service will change the 'from' address to the sender's room JID and delivering the message |
| * to the intended recipient's full JID. |
| * |
| * @param occupant occupant unique room JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul'). |
| * @param listener the listener is a message listener that will handle messages for the newly |
| * created chat. |
| * @return new Chat for sending private messages to a given room occupant. |
| */ |
| public Chat createPrivateChat(String occupant, MessageListener listener) { |
| return connection.getChatManager().createChat(occupant, listener); |
| } |
| |
| /** |
| * Creates a new Message to send to the chat room. |
| * |
| * @return a new Message addressed to the chat room. |
| */ |
| public Message createMessage() { |
| return new Message(room, Message.Type.groupchat); |
| } |
| |
| /** |
| * Sends a Message to the chat room. |
| * |
| * @param message the message. |
| * @throws XMPPException if sending the message fails. |
| */ |
| public void sendMessage(Message message) throws XMPPException { |
| connection.sendPacket(message); |
| } |
| |
| /** |
| * Polls for and returns the next message, or <tt>null</tt> if there isn't |
| * a message immediately available. This method provides significantly different |
| * functionalty than the {@link #nextMessage()} method since it's non-blocking. |
| * In other words, the method call will always return immediately, whereas the |
| * nextMessage method will return only when a message is available (or after |
| * a specific timeout). |
| * |
| * @return the next message if one is immediately available and |
| * <tt>null</tt> otherwise. |
| */ |
| public Message pollMessage() { |
| return (Message) messageCollector.pollResult(); |
| } |
| |
| /** |
| * Returns the next available message in the chat. The method call will block |
| * (not return) until a message is available. |
| * |
| * @return the next message. |
| */ |
| public Message nextMessage() { |
| return (Message) messageCollector.nextResult(); |
| } |
| |
| /** |
| * Returns the next available message in the chat. The method call will block |
| * (not return) until a packet is available or the <tt>timeout</tt> has elapased. |
| * If the timeout elapses without a result, <tt>null</tt> will be returned. |
| * |
| * @param timeout the maximum amount of time to wait for the next message. |
| * @return the next message, or <tt>null</tt> if the timeout elapses without a |
| * message becoming available. |
| */ |
| public Message nextMessage(long timeout) { |
| return (Message) messageCollector.nextResult(timeout); |
| } |
| |
| /** |
| * Adds a packet listener that will be notified of any new messages in the |
| * group chat. Only "group chat" messages addressed to this group chat will |
| * be delivered to the listener. If you wish to listen for other packets |
| * that may be associated with this group chat, you should register a |
| * PacketListener directly with the Connection with the appropriate |
| * PacketListener. |
| * |
| * @param listener a packet listener. |
| */ |
| public void addMessageListener(PacketListener listener) { |
| connection.addPacketListener(listener, messageFilter); |
| connectionListeners.add(listener); |
| } |
| |
| /** |
| * Removes a packet listener that was being notified of any new messages in the |
| * multi user chat. Only "group chat" messages addressed to this multi user chat were |
| * being delivered to the listener. |
| * |
| * @param listener a packet listener. |
| */ |
| public void removeMessageListener(PacketListener listener) { |
| connection.removePacketListener(listener); |
| connectionListeners.remove(listener); |
| } |
| |
| /** |
| * Changes the subject within the room. As a default, only users with a role of "moderator" |
| * are allowed to change the subject in a room. Although some rooms may be configured to |
| * allow a mere participant or even a visitor to change the subject. |
| * |
| * @param subject the new room's subject to set. |
| * @throws XMPPException if someone without appropriate privileges attempts to change the |
| * room subject will throw an error with code 403 (i.e. Forbidden) |
| */ |
| public void changeSubject(final String subject) throws XMPPException { |
| Message message = new Message(room, Message.Type.groupchat); |
| message.setSubject(subject); |
| // Wait for an error or confirmation message back from the server. |
| PacketFilter responseFilter = |
| new AndFilter( |
| new FromMatchesFilter(room), |
| new PacketTypeFilter(Message.class)); |
| responseFilter = new AndFilter(responseFilter, new PacketFilter() { |
| public boolean accept(Packet packet) { |
| Message msg = (Message) packet; |
| return subject.equals(msg.getSubject()); |
| } |
| }); |
| PacketCollector response = connection.createPacketCollector(responseFilter); |
| // Send change subject packet. |
| connection.sendPacket(message); |
| // Wait up to a certain number of seconds for a reply. |
| Message answer = |
| (Message) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); |
| // Stop queuing results |
| response.cancel(); |
| |
| if (answer == null) { |
| throw new XMPPException("No response from server."); |
| } |
| else if (answer.getError() != null) { |
| throw new XMPPException(answer.getError()); |
| } |
| } |
| |
| /** |
| * Notification message that the user has joined the room. |
| */ |
| private synchronized void userHasJoined() { |
| // Update the list of joined rooms through this connection |
| List<String> rooms = joinedRooms.get(connection); |
| if (rooms == null) { |
| rooms = new ArrayList<String>(); |
| joinedRooms.put(connection, rooms); |
| } |
| rooms.add(room); |
| } |
| |
| /** |
| * Notification message that the user has left the room. |
| */ |
| private synchronized void userHasLeft() { |
| // Update the list of joined rooms through this connection |
| List<String> rooms = joinedRooms.get(connection); |
| if (rooms == null) { |
| return; |
| } |
| rooms.remove(room); |
| cleanup(); |
| } |
| |
| /** |
| * Returns the MUCUser packet extension included in the packet or <tt>null</tt> if none. |
| * |
| * @param packet the packet that may include the MUCUser extension. |
| * @return the MUCUser found in the packet. |
| */ |
| private MUCUser getMUCUserExtension(Packet packet) { |
| if (packet != null) { |
| // Get the MUC User extension |
| return (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user"); |
| } |
| return null; |
| } |
| |
| /** |
| * Adds a listener that will be notified of changes in your status in the room |
| * such as the user being kicked, banned, or granted admin permissions. |
| * |
| * @param listener a user status listener. |
| */ |
| public void addUserStatusListener(UserStatusListener listener) { |
| synchronized (userStatusListeners) { |
| if (!userStatusListeners.contains(listener)) { |
| userStatusListeners.add(listener); |
| } |
| } |
| } |
| |
| /** |
| * Removes a listener that was being notified of changes in your status in the room |
| * such as the user being kicked, banned, or granted admin permissions. |
| * |
| * @param listener a user status listener. |
| */ |
| public void removeUserStatusListener(UserStatusListener listener) { |
| synchronized (userStatusListeners) { |
| userStatusListeners.remove(listener); |
| } |
| } |
| |
| private void fireUserStatusListeners(String methodName, Object[] params) { |
| UserStatusListener[] listeners; |
| synchronized (userStatusListeners) { |
| listeners = new UserStatusListener[userStatusListeners.size()]; |
| userStatusListeners.toArray(listeners); |
| } |
| // Get the classes of the method parameters |
| Class<?>[] paramClasses = new Class[params.length]; |
| for (int i = 0; i < params.length; i++) { |
| paramClasses[i] = params[i].getClass(); |
| } |
| try { |
| // Get the method to execute based on the requested methodName and parameters classes |
| Method method = UserStatusListener.class.getDeclaredMethod(methodName, paramClasses); |
| for (UserStatusListener listener : listeners) { |
| method.invoke(listener, params); |
| } |
| } catch (NoSuchMethodException e) { |
| e.printStackTrace(); |
| } catch (InvocationTargetException e) { |
| e.printStackTrace(); |
| } catch (IllegalAccessException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Adds a listener that will be notified of changes in occupants status in the room |
| * such as the user being kicked, banned, or granted admin permissions. |
| * |
| * @param listener a participant status listener. |
| */ |
| public void addParticipantStatusListener(ParticipantStatusListener listener) { |
| synchronized (participantStatusListeners) { |
| if (!participantStatusListeners.contains(listener)) { |
| participantStatusListeners.add(listener); |
| } |
| } |
| } |
| |
| /** |
| * Removes a listener that was being notified of changes in occupants status in the room |
| * such as the user being kicked, banned, or granted admin permissions. |
| * |
| * @param listener a participant status listener. |
| */ |
| public void removeParticipantStatusListener(ParticipantStatusListener listener) { |
| synchronized (participantStatusListeners) { |
| participantStatusListeners.remove(listener); |
| } |
| } |
| |
| private void fireParticipantStatusListeners(String methodName, List<String> params) { |
| ParticipantStatusListener[] listeners; |
| synchronized (participantStatusListeners) { |
| listeners = new ParticipantStatusListener[participantStatusListeners.size()]; |
| participantStatusListeners.toArray(listeners); |
| } |
| try { |
| // Get the method to execute based on the requested methodName and parameter |
| Class<?>[] classes = new Class[params.size()]; |
| for (int i=0;i<params.size(); i++) { |
| classes[i] = String.class; |
| } |
| Method method = ParticipantStatusListener.class.getDeclaredMethod(methodName, classes); |
| for (ParticipantStatusListener listener : listeners) { |
| method.invoke(listener, params.toArray()); |
| } |
| } catch (NoSuchMethodException e) { |
| e.printStackTrace(); |
| } catch (InvocationTargetException e) { |
| e.printStackTrace(); |
| } catch (IllegalAccessException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| private void init() { |
| // Create filters |
| messageFilter = |
| new AndFilter( |
| new FromMatchesFilter(room), |
| new MessageTypeFilter(Message.Type.groupchat)); |
| messageFilter = new AndFilter(messageFilter, new PacketFilter() { |
| public boolean accept(Packet packet) { |
| Message msg = (Message) packet; |
| return msg.getBody() != null; |
| } |
| }); |
| presenceFilter = |
| new AndFilter(new FromMatchesFilter(room), new PacketTypeFilter(Presence.class)); |
| |
| // Create a collector for incoming messages. |
| messageCollector = new ConnectionDetachedPacketCollector(); |
| |
| // Create a listener for subject updates. |
| PacketListener subjectListener = new PacketListener() { |
| public void processPacket(Packet packet) { |
| Message msg = (Message) packet; |
| // Update the room subject |
| subject = msg.getSubject(); |
| // Fire event for subject updated listeners |
| fireSubjectUpdatedListeners( |
| msg.getSubject(), |
| msg.getFrom()); |
| |
| } |
| }; |
| |
| // Create a listener for all presence updates. |
| PacketListener presenceListener = new PacketListener() { |
| public void processPacket(Packet packet) { |
| Presence presence = (Presence) packet; |
| String from = presence.getFrom(); |
| String myRoomJID = room + "/" + nickname; |
| boolean isUserStatusModification = presence.getFrom().equals(myRoomJID); |
| if (presence.getType() == Presence.Type.available) { |
| Presence oldPresence = occupantsMap.put(from, presence); |
| if (oldPresence != null) { |
| // Get the previous occupant's affiliation & role |
| MUCUser mucExtension = getMUCUserExtension(oldPresence); |
| String oldAffiliation = mucExtension.getItem().getAffiliation(); |
| String oldRole = mucExtension.getItem().getRole(); |
| // Get the new occupant's affiliation & role |
| mucExtension = getMUCUserExtension(presence); |
| String newAffiliation = mucExtension.getItem().getAffiliation(); |
| String newRole = mucExtension.getItem().getRole(); |
| // Fire role modification events |
| checkRoleModifications(oldRole, newRole, isUserStatusModification, from); |
| // Fire affiliation modification events |
| checkAffiliationModifications( |
| oldAffiliation, |
| newAffiliation, |
| isUserStatusModification, |
| from); |
| } |
| else { |
| // A new occupant has joined the room |
| if (!isUserStatusModification) { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("joined", params); |
| } |
| } |
| } |
| else if (presence.getType() == Presence.Type.unavailable) { |
| occupantsMap.remove(from); |
| MUCUser mucUser = getMUCUserExtension(presence); |
| if (mucUser != null && mucUser.getStatus() != null) { |
| // Fire events according to the received presence code |
| checkPresenceCode( |
| mucUser.getStatus().getCode(), |
| presence.getFrom().equals(myRoomJID), |
| mucUser, |
| from); |
| } else { |
| // An occupant has left the room |
| if (!isUserStatusModification) { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("left", params); |
| } |
| } |
| } |
| } |
| }; |
| |
| // Listens for all messages that include a MUCUser extension and fire the invitation |
| // rejection listeners if the message includes an invitation rejection. |
| PacketListener declinesListener = new PacketListener() { |
| public void processPacket(Packet packet) { |
| // Get the MUC User extension |
| MUCUser mucUser = getMUCUserExtension(packet); |
| // Check if the MUCUser informs that the invitee has declined the invitation |
| if (mucUser.getDecline() != null && |
| ((Message) packet).getType() != Message.Type.error) { |
| // Fire event for invitation rejection listeners |
| fireInvitationRejectionListeners( |
| mucUser.getDecline().getFrom(), |
| mucUser.getDecline().getReason()); |
| } |
| } |
| }; |
| |
| PacketMultiplexListener packetMultiplexor = new PacketMultiplexListener( |
| messageCollector, presenceListener, subjectListener, |
| declinesListener); |
| |
| roomListenerMultiplexor = RoomListenerMultiplexor.getRoomMultiplexor(connection); |
| |
| roomListenerMultiplexor.addRoom(room, packetMultiplexor); |
| } |
| |
| /** |
| * Fires notification events if the role of a room occupant has changed. If the occupant that |
| * changed his role is your occupant then the <code>UserStatusListeners</code> added to this |
| * <code>MultiUserChat</code> will be fired. On the other hand, if the occupant that changed |
| * his role is not yours then the <code>ParticipantStatusListeners</code> added to this |
| * <code>MultiUserChat</code> will be fired. The following table shows the events that will |
| * be fired depending on the previous and new role of the occupant. |
| * |
| * <pre> |
| * <table border="1"> |
| * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> |
| * |
| * <tr><td>None</td><td>Visitor</td><td>--</td></tr> |
| * <tr><td>Visitor</td><td>Participant</td><td>voiceGranted</td></tr> |
| * <tr><td>Participant</td><td>Moderator</td><td>moderatorGranted</td></tr> |
| * |
| * <tr><td>None</td><td>Participant</td><td>voiceGranted</td></tr> |
| * <tr><td>None</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> |
| * <tr><td>Visitor</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> |
| * |
| * <tr><td>Moderator</td><td>Participant</td><td>moderatorRevoked</td></tr> |
| * <tr><td>Participant</td><td>Visitor</td><td>voiceRevoked</td></tr> |
| * <tr><td>Visitor</td><td>None</td><td>kicked</td></tr> |
| * |
| * <tr><td>Moderator</td><td>Visitor</td><td>voiceRevoked + moderatorRevoked</td></tr> |
| * <tr><td>Moderator</td><td>None</td><td>kicked</td></tr> |
| * <tr><td>Participant</td><td>None</td><td>kicked</td></tr> |
| * </table> |
| * </pre> |
| * |
| * @param oldRole the previous role of the user in the room before receiving the new presence |
| * @param newRole the new role of the user in the room after receiving the new presence |
| * @param isUserModification whether the received presence is about your user in the room or not |
| * @param from the occupant whose role in the room has changed |
| * (e.g. room@conference.jabber.org/nick). |
| */ |
| private void checkRoleModifications( |
| String oldRole, |
| String newRole, |
| boolean isUserModification, |
| String from) { |
| // Voice was granted to a visitor |
| if (("visitor".equals(oldRole) || "none".equals(oldRole)) |
| && "participant".equals(newRole)) { |
| if (isUserModification) { |
| fireUserStatusListeners("voiceGranted", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("voiceGranted", params); |
| } |
| } |
| // The participant's voice was revoked from the room |
| else if ( |
| "participant".equals(oldRole) |
| && ("visitor".equals(newRole) || "none".equals(newRole))) { |
| if (isUserModification) { |
| fireUserStatusListeners("voiceRevoked", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("voiceRevoked", params); |
| } |
| } |
| // Moderator privileges were granted to a participant |
| if (!"moderator".equals(oldRole) && "moderator".equals(newRole)) { |
| if ("visitor".equals(oldRole) || "none".equals(oldRole)) { |
| if (isUserModification) { |
| fireUserStatusListeners("voiceGranted", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("voiceGranted", params); |
| } |
| } |
| if (isUserModification) { |
| fireUserStatusListeners("moderatorGranted", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("moderatorGranted", params); |
| } |
| } |
| // Moderator privileges were revoked from a participant |
| else if ("moderator".equals(oldRole) && !"moderator".equals(newRole)) { |
| if ("visitor".equals(newRole) || "none".equals(newRole)) { |
| if (isUserModification) { |
| fireUserStatusListeners("voiceRevoked", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("voiceRevoked", params); |
| } |
| } |
| if (isUserModification) { |
| fireUserStatusListeners("moderatorRevoked", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("moderatorRevoked", params); |
| } |
| } |
| } |
| |
| /** |
| * Fires notification events if the affiliation of a room occupant has changed. If the |
| * occupant that changed his affiliation is your occupant then the |
| * <code>UserStatusListeners</code> added to this <code>MultiUserChat</code> will be fired. |
| * On the other hand, if the occupant that changed his affiliation is not yours then the |
| * <code>ParticipantStatusListeners</code> added to this <code>MultiUserChat</code> will be |
| * fired. The following table shows the events that will be fired depending on the previous |
| * and new affiliation of the occupant. |
| * |
| * <pre> |
| * <table border="1"> |
| * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> |
| * |
| * <tr><td>None</td><td>Member</td><td>membershipGranted</td></tr> |
| * <tr><td>Member</td><td>Admin</td><td>membershipRevoked + adminGranted</td></tr> |
| * <tr><td>Admin</td><td>Owner</td><td>adminRevoked + ownershipGranted</td></tr> |
| * |
| * <tr><td>None</td><td>Admin</td><td>adminGranted</td></tr> |
| * <tr><td>None</td><td>Owner</td><td>ownershipGranted</td></tr> |
| * <tr><td>Member</td><td>Owner</td><td>membershipRevoked + ownershipGranted</td></tr> |
| * |
| * <tr><td>Owner</td><td>Admin</td><td>ownershipRevoked + adminGranted</td></tr> |
| * <tr><td>Admin</td><td>Member</td><td>adminRevoked + membershipGranted</td></tr> |
| * <tr><td>Member</td><td>None</td><td>membershipRevoked</td></tr> |
| * |
| * <tr><td>Owner</td><td>Member</td><td>ownershipRevoked + membershipGranted</td></tr> |
| * <tr><td>Owner</td><td>None</td><td>ownershipRevoked</td></tr> |
| * <tr><td>Admin</td><td>None</td><td>adminRevoked</td></tr> |
| * <tr><td><i>Anyone</i></td><td>Outcast</td><td>banned</td></tr> |
| * </table> |
| * </pre> |
| * |
| * @param oldAffiliation the previous affiliation of the user in the room before receiving the |
| * new presence |
| * @param newAffiliation the new affiliation of the user in the room after receiving the new |
| * presence |
| * @param isUserModification whether the received presence is about your user in the room or not |
| * @param from the occupant whose role in the room has changed |
| * (e.g. room@conference.jabber.org/nick). |
| */ |
| private void checkAffiliationModifications( |
| String oldAffiliation, |
| String newAffiliation, |
| boolean isUserModification, |
| String from) { |
| // First check for revoked affiliation and then for granted affiliations. The idea is to |
| // first fire the "revoke" events and then fire the "grant" events. |
| |
| // The user's ownership to the room was revoked |
| if ("owner".equals(oldAffiliation) && !"owner".equals(newAffiliation)) { |
| if (isUserModification) { |
| fireUserStatusListeners("ownershipRevoked", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("ownershipRevoked", params); |
| } |
| } |
| // The user's administrative privileges to the room were revoked |
| else if ("admin".equals(oldAffiliation) && !"admin".equals(newAffiliation)) { |
| if (isUserModification) { |
| fireUserStatusListeners("adminRevoked", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("adminRevoked", params); |
| } |
| } |
| // The user's membership to the room was revoked |
| else if ("member".equals(oldAffiliation) && !"member".equals(newAffiliation)) { |
| if (isUserModification) { |
| fireUserStatusListeners("membershipRevoked", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("membershipRevoked", params); |
| } |
| } |
| |
| // The user was granted ownership to the room |
| if (!"owner".equals(oldAffiliation) && "owner".equals(newAffiliation)) { |
| if (isUserModification) { |
| fireUserStatusListeners("ownershipGranted", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("ownershipGranted", params); |
| } |
| } |
| // The user was granted administrative privileges to the room |
| else if (!"admin".equals(oldAffiliation) && "admin".equals(newAffiliation)) { |
| if (isUserModification) { |
| fireUserStatusListeners("adminGranted", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("adminGranted", params); |
| } |
| } |
| // The user was granted membership to the room |
| else if (!"member".equals(oldAffiliation) && "member".equals(newAffiliation)) { |
| if (isUserModification) { |
| fireUserStatusListeners("membershipGranted", new Object[] {}); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| fireParticipantStatusListeners("membershipGranted", params); |
| } |
| } |
| } |
| |
| /** |
| * Fires events according to the received presence code. |
| * |
| * @param code |
| * @param isUserModification |
| * @param mucUser |
| * @param from |
| */ |
| private void checkPresenceCode( |
| String code, |
| boolean isUserModification, |
| MUCUser mucUser, |
| String from) { |
| // Check if an occupant was kicked from the room |
| if ("307".equals(code)) { |
| // Check if this occupant was kicked |
| if (isUserModification) { |
| joined = false; |
| |
| fireUserStatusListeners( |
| "kicked", |
| new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()}); |
| |
| // Reset occupant information. |
| occupantsMap.clear(); |
| nickname = null; |
| userHasLeft(); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| params.add(mucUser.getItem().getActor()); |
| params.add(mucUser.getItem().getReason()); |
| fireParticipantStatusListeners("kicked", params); |
| } |
| } |
| // A user was banned from the room |
| else if ("301".equals(code)) { |
| // Check if this occupant was banned |
| if (isUserModification) { |
| joined = false; |
| |
| fireUserStatusListeners( |
| "banned", |
| new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()}); |
| |
| // Reset occupant information. |
| occupantsMap.clear(); |
| nickname = null; |
| userHasLeft(); |
| } |
| else { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| params.add(mucUser.getItem().getActor()); |
| params.add(mucUser.getItem().getReason()); |
| fireParticipantStatusListeners("banned", params); |
| } |
| } |
| // A user's membership was revoked from the room |
| else if ("321".equals(code)) { |
| // Check if this occupant's membership was revoked |
| if (isUserModification) { |
| joined = false; |
| |
| fireUserStatusListeners("membershipRevoked", new Object[] {}); |
| |
| // Reset occupant information. |
| occupantsMap.clear(); |
| nickname = null; |
| userHasLeft(); |
| } |
| } |
| // A occupant has changed his nickname in the room |
| else if ("303".equals(code)) { |
| List<String> params = new ArrayList<String>(); |
| params.add(from); |
| params.add(mucUser.getItem().getNick()); |
| fireParticipantStatusListeners("nicknameChanged", params); |
| } |
| } |
| |
| private void cleanup() { |
| try { |
| if (connection != null) { |
| roomListenerMultiplexor.removeRoom(room); |
| // Remove all the PacketListeners added to the connection by this chat |
| for (PacketListener connectionListener : connectionListeners) { |
| connection.removePacketListener(connectionListener); |
| } |
| } |
| } catch (Exception e) { |
| // Do nothing |
| } |
| } |
| |
| protected void finalize() throws Throwable { |
| cleanup(); |
| super.finalize(); |
| } |
| |
| /** |
| * An InvitationsMonitor monitors a given connection to detect room invitations. Every |
| * time the InvitationsMonitor detects a new invitation it will fire the invitation listeners. |
| * |
| * @author Gaston Dombiak |
| */ |
| private static class InvitationsMonitor implements ConnectionListener { |
| // We use a WeakHashMap so that the GC can collect the monitor when the |
| // connection is no longer referenced by any object. |
| // Note that when the InvitationsMonitor is used, i.e. when there are InvitationListeners, it will add a |
| // PacketListener to the Connection and therefore a strong reference from the Connection to the |
| // InvitationsMonior will exists, preventing it from beeing gc'ed. After the last InvitationListener is gone, |
| // the PacketListener will get removed (cancel()) allowing the garbage collection of the InvitationsMonitor |
| // instance. |
| private final static Map<Connection, WeakReference<InvitationsMonitor>> monitors = |
| new WeakHashMap<Connection, WeakReference<InvitationsMonitor>>(); |
| |
| // We don't use a synchronized List here because it would break the semantic of (add|remove)InvitationListener |
| private final List<InvitationListener> invitationsListeners = |
| new ArrayList<InvitationListener>(); |
| private Connection connection; |
| private PacketFilter invitationFilter; |
| private PacketListener invitationPacketListener; |
| |
| /** |
| * Returns a new or existing InvitationsMonitor for a given connection. |
| * |
| * @param conn the connection to monitor for room invitations. |
| * @return a new or existing InvitationsMonitor for a given connection. |
| */ |
| public static InvitationsMonitor getInvitationsMonitor(Connection conn) { |
| synchronized (monitors) { |
| if (!monitors.containsKey(conn) || monitors.get(conn).get() == null) { |
| // We need to use a WeakReference because the monitor references the |
| // connection and this could prevent the GC from collecting the monitor |
| // when no other object references the monitor |
| InvitationsMonitor ivm = new InvitationsMonitor(conn); |
| monitors.put(conn, new WeakReference<InvitationsMonitor>(ivm)); |
| return ivm; |
| } |
| // Return the InvitationsMonitor that monitors the connection |
| return monitors.get(conn).get(); |
| } |
| } |
| |
| /** |
| * Creates a new InvitationsMonitor that will monitor invitations received |
| * on a given connection. |
| * |
| * @param connection the connection to monitor for possible room invitations |
| */ |
| private InvitationsMonitor(Connection connection) { |
| this.connection = connection; |
| } |
| |
| /** |
| * Adds a listener to invitation notifications. The listener will be fired anytime |
| * an invitation is received.<p> |
| * |
| * If this is the first monitor's listener then the monitor will be initialized in |
| * order to start listening to room invitations. |
| * |
| * @param listener an invitation listener. |
| */ |
| public void addInvitationListener(InvitationListener listener) { |
| synchronized (invitationsListeners) { |
| // If this is the first monitor's listener then initialize the listeners |
| // on the connection to detect room invitations |
| if (invitationsListeners.size() == 0) { |
| init(); |
| } |
| if (!invitationsListeners.contains(listener)) { |
| invitationsListeners.add(listener); |
| } |
| } |
| } |
| |
| /** |
| * Removes a listener to invitation notifications. The listener will be fired anytime |
| * an invitation is received.<p> |
| * |
| * If there are no more listeners to notifiy for room invitations then the monitor will |
| * be stopped. As soon as a new listener is added to the monitor, the monitor will resume |
| * monitoring the connection for new room invitations. |
| * |
| * @param listener an invitation listener. |
| */ |
| public void removeInvitationListener(InvitationListener listener) { |
| synchronized (invitationsListeners) { |
| if (invitationsListeners.contains(listener)) { |
| invitationsListeners.remove(listener); |
| } |
| // If there are no more listeners to notifiy for room invitations |
| // then proceed to cancel/release this monitor |
| if (invitationsListeners.size() == 0) { |
| cancel(); |
| } |
| } |
| } |
| |
| /** |
| * Fires invitation listeners. |
| */ |
| private void fireInvitationListeners(String room, String inviter, String reason, String password, |
| Message message) { |
| InvitationListener[] listeners; |
| synchronized (invitationsListeners) { |
| listeners = new InvitationListener[invitationsListeners.size()]; |
| invitationsListeners.toArray(listeners); |
| } |
| for (InvitationListener listener : listeners) { |
| listener.invitationReceived(connection, room, inviter, reason, password, message); |
| } |
| } |
| |
| public void connectionClosed() { |
| cancel(); |
| } |
| |
| public void connectionClosedOnError(Exception e) { |
| // ignore |
| } |
| |
| public void reconnectingIn(int seconds) { |
| // ignore |
| } |
| |
| public void reconnectionSuccessful() { |
| // ignore |
| } |
| |
| public void reconnectionFailed(Exception e) { |
| // ignore |
| } |
| |
| /** |
| * Initializes the listeners to detect received room invitations and to detect when the |
| * connection gets closed. As soon as a room invitation is received the invitations |
| * listeners will be fired. When the connection gets closed the monitor will remove |
| * his listeners on the connection. |
| */ |
| private void init() { |
| // Listens for all messages that include a MUCUser extension and fire the invitation |
| // listeners if the message includes an invitation. |
| invitationFilter = |
| new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user"); |
| invitationPacketListener = new PacketListener() { |
| public void processPacket(Packet packet) { |
| // Get the MUCUser extension |
| MUCUser mucUser = |
| (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user"); |
| // Check if the MUCUser extension includes an invitation |
| if (mucUser.getInvite() != null && |
| ((Message) packet).getType() != Message.Type.error) { |
| // Fire event for invitation listeners |
| fireInvitationListeners(packet.getFrom(), mucUser.getInvite().getFrom(), |
| mucUser.getInvite().getReason(), mucUser.getPassword(), (Message) packet); |
| } |
| } |
| }; |
| connection.addPacketListener(invitationPacketListener, invitationFilter); |
| // Add a listener to detect when the connection gets closed in order to |
| // cancel/release this monitor |
| connection.addConnectionListener(this); |
| } |
| |
| /** |
| * Cancels all the listeners that this InvitationsMonitor has added to the connection. |
| */ |
| private void cancel() { |
| connection.removePacketListener(invitationPacketListener); |
| connection.removeConnectionListener(this); |
| } |
| |
| } |
| } |