diff --git a/documentation/extensions/muc.md b/documentation/extensions/muc.md index 9c3aa3644..4ed2fb215 100644 --- a/documentation/extensions/muc.md +++ b/documentation/extensions/muc.md @@ -31,8 +31,9 @@ allowed to enter. **Usage** In order to create a room you will need to first create an instance of -_**MultiUserChat**_. The room name passed to the constructor will be the name -of the room to create. The next step is to send **create(String nickname)** to +_**MultiUserChat**_. +In order to do so, get a instance of `MultiUserChatManager` and call `getMultiUserChat(String)` to retrieve a `MultiUserChat` instance. +The next step is to send **create(String nickname)** to the _**MultiUserChat**_ instance where nickname is the nickname to use when joining the room. @@ -46,9 +47,12 @@ configuration form, complete the form and finally send it back to the server. In this example we can see how to create an instant room: -``` -// Create a MultiUserChat using a XMPPConnection for a room -MultiUserChat muc = new MultiUserChat(conn1, "myroom@conference.jabber.org"); +```java +// Get the MultiUserChatManager +MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection); + +// Get a MultiUserChat using MultiUserChatManager +MultiUserChat muc = manager.getMultiUserChat("myroom@conference.jabber.org"); // Create the room muc.create("testbot"); @@ -61,9 +65,12 @@ muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT)); In this example we can see how to create a reserved room. The form is completed with default values: +```java +// Get the MultiUserChatManager +MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection); // Create a MultiUserChat using a XMPPConnection for a room -MultiUserChat muc = new MultiUserChat(conn1, "myroom@conference.jabber.org"); +MultiUserChat muc = = manager.getMultiUserChat("myroom@conference.jabber.org"); // Create the room muc.create("testbot"); @@ -101,9 +108,10 @@ room is password protected. **Usage** -In order to join a room you will need to first create an instance of -_**MultiUserChat**_. The room name passed to the constructor will be the name -of the room to join. The next step is to send **join(...)** to the +In order to join a room you will need to first get an instance of +_**MultiUserChat**_. +In order to do so, get a instance of `MultiUserChatManager` and call `getMultiUserChat(String)` to retrieve a `MultiUserChat` instance. +The next step is to send **join(...)** to the _**MultiUserChat**_ instance. But first you will have to decide which join message to send. If you want to just join the room without a password and without specifying the amount of history to receive then you could use @@ -120,9 +128,13 @@ wait for a response from the server. In this example we can see how to join a room with a given nickname: -``` +```java +// Get the MultiUserChatManager +MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection); + // Create a MultiUserChat using a XMPPConnection for a room -MultiUserChat muc2 = new MultiUserChat(conn1, "myroom@conference.jabber.org"); +MultiUserChat muc2 = manager.getMultiUserChat("myroom@conference.jabber.org"); + // User2 joins the new room // The room service will decide the amount of history to send muc2.join("testbot2"); @@ -131,9 +143,12 @@ muc2.join("testbot2"); In this example we can see how to join a room with a given nickname and password: -``` +```java +// Get the MultiUserChatManager +MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection); + // Create a MultiUserChat using a XMPPConnection for a room -MultiUserChat muc2 = new MultiUserChat(conn1, "myroom@conference.jabber.org"); +MultiUserChat muc2 = manager.getMultiUserChat("myroom@conference.jabber.org"); // User2 joins the new room using a password // The room service will decide the amount of history to send @@ -143,9 +158,12 @@ muc2.join("testbot2", "password"); In this example we can see how to join a room with a given nickname specifying the amount of history to receive: -``` +```java +// Get the MultiUserChatManager +MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection); + // Create a MultiUserChat using a XMPPConnection for a room -MultiUserChat muc2 = new MultiUserChat(conn1, "myroom@conference.jabber.org"); +MultiUserChat muc2 = manager.getMultiUserChat("myroom@conference.jabber.org"); // User2 joins the new room using a password and specifying // the amount of history to receive. In this example we are requesting the last 5 messages. @@ -175,7 +193,7 @@ to the room (e.g. hecate@shakespeare.lit) and reason is the reason why the user is being invited. If potential invitees want to listen for room invitations then the invitee -must add an _**InvitationListener**_ to the _**MultiUserChat**_ class. Since +must add an _**InvitationListener**_ to the _**MultiUserChatManager**_ class. Since the _**InvitationListener**_ is an _interface_, it is necessary to create a class that implements this _interface_. If an inviter wants to listen for room invitation rejections, just add an _**InvitationRejectionListener**_ to the @@ -187,9 +205,13 @@ you will need to create a class that implements this interface. In this example we can see how to invite another user to the room and lister for possible rejections: -``` -// User2 joins the room -MultiUserChat muc2 = new MultiUserChat(conn2, room); +```java +// Get the MultiUserChatManager +MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection); + +// Create a MultiUserChat using a XMPPConnection for a room +MultiUserChat muc2 = manager.getMultiUserChat("myroom@conference.jabber.org"); + muc2.join("testbot2"); // User2 listens for invitation rejections muc2.addInvitationRejectionListener(new InvitationRejectionListener() { @@ -204,9 +226,9 @@ muc2.invite("user3@host.org/Smack", "Meet me in this excellent room"); In this example we can see how to listen for room invitations and decline invitations: -``` +```java // User3 listens for MUC invitations -MultiUserChat.addInvitationListener(conn3, new InvitationListener() { +MultiUserChatManager.getInstanceFor(connection).addInvitationListener(new InvitationListener() { public void invitationReceived(XMPPConnection conn, String room, String inviter, String reason, String password) { // Reject the invitation MultiUserChat.decline(conn, room, inviter, "I'm busy right now"); @@ -225,8 +247,8 @@ User Chat protocol. **Usage** In order to discover if one of the user's contacts supports MUC just send -**isServiceEnabled(XMPPConnection connection, String user)** to the -_**MultiUserChat**_ class where user is a fully qualified XMPP ID, e.g. +**isServiceEnabled(String user)** to the +_**MultiUserChatManager**_ class where user is a fully qualified XMPP ID, e.g. jdoe@example.com. You will receive a boolean indicating whether the user supports MUC or not. @@ -234,9 +256,9 @@ supports MUC or not. In this example we can see how to discover support of MUC: -``` +```java // Discover whether user3@host.org supports MUC or not -boolean supports = MultiUserChat.isServiceEnabled(conn, "user3@host.org/Smack"); +boolean supports = MultiUserChatManager.getInstanceFor(connection).isServiceEnabled("user3@host.org/Smack"); ``` Discover joined rooms @@ -250,8 +272,8 @@ in. **Usage** In order to get the rooms where a user is in just send -**getJoinedRooms(XMPPConnection connection, String user)** to the -_**MultiUserChat**_ class where user is a fully qualified XMPP ID, e.g. +**getJoinedRooms(String user)** to the +_**MultiUserChatManager**_ class where user is a fully qualified XMPP ID, e.g. jdoe@example.com. You will get an Iterator of Strings as an answer where each String represents a room name. @@ -259,9 +281,12 @@ String represents a room name. In this example we can see how to get the rooms where a user is in: -``` +```java +// Get the MultiUserChatManager +MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection); + // Get the rooms where user3@host.org has joined -Iterator joinedRooms = MultiUserChat.getJoinedRooms(conn, "user3@host.org/Smack"); +List joinedRooms = manager.getJoinedRooms("user3@host.org/Smack"); ``` Discover room information @@ -276,8 +301,8 @@ rooms. **Usage** In order to discover information about a room just send -**getRoomInfo(XMPPConnection connection, String room)** to the -_**MultiUserChat**_ class where room is the XMPP ID of the room, e.g. +**getRoomInfo(String room)** to the +_**MultiUserChatManager**_ class where room is the XMPP ID of the room, e.g. roomName@conference.myserver. You will get a RoomInfo object that contains the discovered room information. @@ -285,9 +310,12 @@ discovered room information. In this example we can see how to discover information about a room: -``` +```java +// Get the MultiUserChatManager +MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection); + // Discover information about the room roomName@conference.myserver -RoomInfo info = MultiUserChat.getRoomInfo(conn, "roomName@conference.myserver"); +RoomInfo info = manager.getRoomInfo("roomName@conference.myserver"); System.out.println("Number of occupants:" + info.getOccupantsCount()); System.out.println("Room Subject:" + info.getSubject()); ``` @@ -440,17 +468,18 @@ These are the triggered events when the role has been downgraded: In this example we can see how to grant voice to a visitor and listen for the notification events: -``` +```java // User1 creates a room -muc = new MultiUserChat(conn1, "myroom@conference.jabber.org"); +muc = manager.getMultiUserChat("myroom@conference.jabber.org"); muc.create("testbot"); // User1 (which is the room owner) configures the room as a moderated room Form form = muc.getConfigurationForm(); Form answerForm = form.createAnswerForm(); answerForm.setAnswer("muc#roomconfig_moderatedroom", "1"); muc.sendConfigurationForm(answerForm); + // User2 joins the new room (as a visitor) -MultiUserChat muc2 = new MultiUserChat(conn2, "myroom@conference.jabber.org"); +MultiUserChat muc2 = manager2.getMultiUserChat("myroom@conference.jabber.org"); muc2.join("testbot2"); // User2 will listen for his own "voice" notification events muc2.addUserStatusListener(new DefaultUserStatusListener() { @@ -463,8 +492,9 @@ muc2.addUserStatusListener(new DefaultUserStatusListener() { ... } }); + // User3 joins the new room (as a visitor) -MultiUserChat muc3 = new MultiUserChat(conn3, "myroom@conference.jabber.org"); +MultiUserChat muc3 = manager3.getMultiUserChat("myroom@conference.jabber.org"); muc3.join("testbot3"); // User3 will lister for other occupants "voice" notification events muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { @@ -578,17 +608,18 @@ These are the triggered events when the affiliation has been downgraded: In this example we can see how to grant admin privileges to a user and listen for the notification events: -``` +```java // User1 creates a room -muc = new MultiUserChat(conn1, "myroom@conference.jabber.org"); +muc = manager.getMultiUserChat("myroom@conference.jabber.org"); muc.create("testbot"); // User1 (which is the room owner) configures the room as a moderated room Form form = muc.getConfigurationForm(); Form answerForm = form.createAnswerForm(); answerForm.setAnswer("muc#roomconfig_moderatedroom", "1"); muc.sendConfigurationForm(answerForm); + // User2 joins the new room (as a visitor) -MultiUserChat muc2 = new MultiUserChat(conn2, "myroom@conference.jabber.org"); +MultiUserChat muc2 = manager2.getMultiUserChat("myroom@conference.jabber.org"); muc2.join("testbot2"); // User2 will listen for his own admin privileges muc2.addUserStatusListener(new DefaultUserStatusListener() { @@ -601,8 +632,9 @@ muc2.addUserStatusListener(new DefaultUserStatusListener() { ... } }); + // User3 joins the new room (as a visitor) -MultiUserChat muc3 = new MultiUserChat(conn3, "myroom@conference.jabber.org"); +MultiUserChat muc3 = manager3.getMultiUserChat("myroom@conference.jabber.org"); muc3.join("testbot3"); // User3 will lister for other users admin privileges muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/AbstractNodeInformationProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/AbstractNodeInformationProvider.java new file mode 100644 index 000000000..7cfb6d2fd --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/AbstractNodeInformationProvider.java @@ -0,0 +1,47 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * 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.disco; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; +import org.jivesoftware.smackx.disco.packet.DiscoverItems; + +import java.util.List; + + +/** + * + */ +public abstract class AbstractNodeInformationProvider implements NodeInformationProvider { + + public List getNodeItems() { + return null; + } + + public List getNodeFeatures() { + return null; + } + + public List getNodeIdentities() { + return null; + } + + public List getNodePacketExtensions() { + return null; + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/HostedRoom.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/HostedRoom.java index ab8066890..fa5533293 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/HostedRoom.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/HostedRoom.java @@ -22,9 +22,9 @@ import org.jivesoftware.smackx.disco.packet.DiscoverItems; * Hosted rooms by a chat service may be discovered if they are configured to appear in the room * directory . The information that may be discovered is the XMPP address of the room and the room * name. The address of the room may be used for obtaining more detailed information - * {@link org.jivesoftware.smackx.muc.MultiUserChat#getRoomInfo(org.jivesoftware.smack.XMPPConnection, String)} + * {@link org.jivesoftware.smackx.muc.MultiUserChatManager#getRoomInfo(String)} * or could be used for joining the room - * {@link org.jivesoftware.smackx.muc.MultiUserChat#MultiUserChat(org.jivesoftware.smack.XMPPConnection, String)} + * {@link org.jivesoftware.smackx.muc.MultiUserChatManager#getMultiUserChat(String)} * and {@link org.jivesoftware.smackx.muc.MultiUserChat#join(String)}. * * @author Gaston Dombiak diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index d651f7b74..179e15524 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -17,7 +17,6 @@ package org.jivesoftware.smackx.muc; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -25,7 +24,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; @@ -34,8 +32,6 @@ import java.util.logging.Logger; import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.ChatManager; import org.jivesoftware.smack.ChatMessageListener; -import org.jivesoftware.smack.ConnectionCreationListener; -import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.MessageListener; import org.jivesoftware.smack.PacketCollector; import org.jivesoftware.smack.PacketListener; @@ -44,7 +40,6 @@ import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; -import org.jivesoftware.smack.XMPPConnectionRegistry; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.filter.AndFilter; @@ -59,13 +54,10 @@ import org.jivesoftware.smack.filter.ToFilter; 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.util.StringUtils; -import org.jivesoftware.smackx.disco.NodeInformationProvider; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; -import org.jivesoftware.smackx.disco.packet.DiscoverItems; import org.jivesoftware.smackx.iqregister.packet.Registration; import org.jivesoftware.smackx.muc.packet.Destroy; import org.jivesoftware.smackx.muc.packet.MUCAdmin; @@ -92,14 +84,10 @@ import org.jivesoftware.smackx.xdata.Form; */ public class MultiUserChat { private static final Logger LOGGER = Logger.getLogger(MultiUserChat.class.getName()); - - private final static String DISCO_NODE = MUCInitialPresence.NAMESPACE + "#rooms"; - - private static Map> joinedRooms = - new WeakHashMap>(); private final XMPPConnection connection; private final String room; + private final MultiUserChatManager multiUserChatManager; private final Map occupantsMap = new ConcurrentHashMap(); private final Set invitationRejectionListeners = new CopyOnWriteArraySet(); @@ -133,65 +121,10 @@ public class MultiUserChat { private boolean joined = false; private PacketCollector messageCollector; - static { - XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { - public void connectionCreated(final XMPPConnection 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(MUCInitialPresence.NAMESPACE); - - // Set the NodeInformationProvider that will provide information about the - // joined rooms whenever a disco request is received - final WeakReference weakRefConnection = new WeakReference(connection); - ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider( - DISCO_NODE, - new NodeInformationProvider() { - public List getNodeItems() { - XMPPConnection connection = weakRefConnection.get(); - if (connection == null) return Collections.emptyList(); - List answer = new ArrayList(); - for (String room : MultiUserChat.getJoinedRooms(connection)) { - answer.add(new DiscoverItems.Item(room)); - } - return answer; - } - - public List getNodeFeatures() { - return null; - } - - public List getNodeIdentities() { - return null; - } - - @Override - public List 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.

- * - * 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(XMPPConnection connection, String room) { + MultiUserChat(XMPPConnection connection, String room, MultiUserChatManager multiUserChatManager) { this.connection = connection; this.room = room.toLowerCase(Locale.US); + this.multiUserChatManager = multiUserChatManager; fromRoomFilter = FromMatchesFilter.create(room); fromRoomGroupchatFilter = new AndFilter(fromRoomFilter, MessageTypeFilter.GROUPCHAT); @@ -306,117 +239,6 @@ public class MultiUserChat { }; } - /** - * 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. - * @throws XMPPErrorException - * @throws NoResponseException - * @throws NotConnectedException - */ - public static boolean isServiceEnabled(XMPPConnection connection, String user) - throws NoResponseException, XMPPErrorException, NotConnectedException { - return ServiceDiscoveryManager.getInstanceFor(connection).supportsFeature(user, - MUCInitialPresence.NAMESPACE); - } - - /** - * Returns a List of 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 a List of the rooms where the user has joined using a given connection. - */ - private static List getJoinedRooms(XMPPConnection connection) { - List rooms = joinedRooms.get(connection); - if (rooms != null) { - return rooms; - } - // Return an empty collection (i.e. the user never joined a room) - return Collections.emptyList(); - } - - /** - * Returns a List of 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 a List of the rooms where the requested user has joined. - * @throws XMPPErrorException - * @throws NoResponseException - * @throws NotConnectedException - */ - public static List getJoinedRooms(XMPPConnection connection, String user) - throws NoResponseException, XMPPErrorException, NotConnectedException { - ArrayList answer = new ArrayList(); - // Send the disco packet to the user - DiscoverItems result = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems( - user, DISCO_NODE); - // Collect the entityID for each returned item - for (DiscoverItems.Item item : result.getItems()) { - answer.add(item.getEntityID()); - } - return answer; - } - - /** - * 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 XMPPErrorException - * @throws NoResponseException - * @throws NotConnectedException - */ - public static RoomInfo getRoomInfo(XMPPConnection connection, String room) - throws NoResponseException, XMPPErrorException, NotConnectedException { - 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 XMPPErrorException - * @throws NoResponseException - * @throws NotConnectedException - */ - public static List getServiceNames(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException { - ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection); - return sdm.findServices(MUCInitialPresence.NAMESPACE, false, false); - } - - /** - * 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 XMPPErrorException - * @throws NoResponseException - * @throws NotConnectedException - */ - public static Collection getHostedRooms(XMPPConnection connection, - String serviceName) throws NoResponseException, XMPPErrorException, NotConnectedException { - List answer = new ArrayList(); - ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); - DiscoverItems items = discoManager.discoverItems(serviceName); - for (DiscoverItems.Item item : items.getItems()) { - answer.add(new HostedRoom(item)); - } - return answer; - } /** * Returns the name of the room this MultiUserChat object represents. @@ -483,13 +305,8 @@ public class MultiUserChat { connection.addPacketInterceptor(presenceInterceptor, new AndFilter(new ToFilter(room), PacketTypeFilter.PRESENCE)); messageCollector = connection.createPacketCollector(fromRoomGroupchatFilter); - // Update the list of joined rooms through this connection - List rooms = joinedRooms.get(connection); - if (rooms == null) { - rooms = new ArrayList(); - joinedRooms.put(connection, rooms); - } - rooms.add(room); + // Update the list of joined rooms + multiUserChatManager.addJoinedRoom(room); return presence; } @@ -841,53 +658,6 @@ public class MultiUserChat { 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. - * @throws NotConnectedException - */ - public static void decline(XMPPConnection conn, String room, String inviter, String reason) throws NotConnectedException { - 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(XMPPConnection 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(XMPPConnection 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. @@ -1900,11 +1670,8 @@ public class MultiUserChat { * Notification message that the user has left the room. */ private synchronized void userHasLeft() { - // Update the list of joined rooms through this connection - List rooms = joinedRooms.get(connection); - if (rooms == null) { - return; - } + // Update the list of joined rooms + multiUserChatManager.removeJoinedRoom(room); connection.removePacketListener(messageListener); connection.removePacketListener(presenceListener); connection.removePacketListener(declinesListener); @@ -1913,7 +1680,6 @@ public class MultiUserChat { messageCollector.cancel(); messageCollector = null; } - rooms.remove(room); } /** @@ -2286,91 +2052,4 @@ public class MultiUserChat { } } } - - /** - * 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 extends Manager { - - private static Map INSTANCES = new WeakHashMap(); - - private static final PacketFilter invitationFilter = new PacketExtensionFilter(new MUCUser()); - - private final Set invitationsListeners = new CopyOnWriteArraySet(); - private final 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 synchronized InvitationsMonitor getInvitationsMonitor(XMPPConnection conn) { - InvitationsMonitor invitationsMonitor = INSTANCES.get(conn); - if (invitationsMonitor == null) { - invitationsMonitor = new InvitationsMonitor(conn); - INSTANCES.put(conn, invitationsMonitor); - } - return invitationsMonitor; - } - - /** - * 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(XMPPConnection connection) { - super(connection); - // Listens for all messages that include a MUCUser extension and fire the invitation - // listeners if the message includes an invitation. - invitationPacketListener = new PacketListener() { - public void processPacket(Packet packet) { - // Get the MUCUser extension - MUCUser mucUser = MUCUser.from(packet); - // 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); - } - - /** - * Adds a listener to invitation notifications. The listener will be fired anytime - * an invitation is received. - * - * @param listener an invitation listener. - */ - public void addInvitationListener(InvitationListener listener) { - invitationsListeners.add(listener); - } - - /** - * Removes a listener to invitation notifications. The listener will be fired anytime - * an invitation is received. - * - * @param listener an invitation listener. - */ - public void removeInvitationListener(InvitationListener listener) { - invitationsListeners.remove(listener); - } - - /** - * Fires invitation listeners. - */ - private void fireInvitationListeners(String room, String inviter, String reason, String password, - Message message) { - for (InvitationListener listener : invitationsListeners) { - listener.invitationReceived(connection(), room, inviter, reason, password, message); - } - } - } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java new file mode 100644 index 000000000..c1863c915 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java @@ -0,0 +1,305 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * 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.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPConnectionRegistry; +import org.jivesoftware.smack.SmackException.NoResponseException; +import org.jivesoftware.smack.SmackException.NotConnectedException; +import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.NotFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.disco.AbstractNodeInformationProvider; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; +import org.jivesoftware.smackx.disco.packet.DiscoverItems; +import org.jivesoftware.smackx.muc.packet.MUCInitialPresence; +import org.jivesoftware.smackx.muc.packet.MUCUser; + +public class MultiUserChatManager extends Manager { + private final static String DISCO_NODE = MUCInitialPresence.NAMESPACE + "#rooms"; + + static { + XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(final XMPPConnection 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(MUCInitialPresence.NAMESPACE); + + // Set the NodeInformationProvider that will provide information about the + // joined rooms whenever a disco request is received + final WeakReference weakRefConnection = new WeakReference(connection); + ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(DISCO_NODE, + new AbstractNodeInformationProvider() { + @Override + public List getNodeItems() { + XMPPConnection connection = weakRefConnection.get(); + if (connection == null) + return Collections.emptyList(); + Set joinedRooms = MultiUserChatManager.getInstanceFor(connection).getJoinedRooms(); + List answer = new ArrayList(); + for (String room : joinedRooms) { + answer.add(new DiscoverItems.Item(room)); + } + return answer; + } + }); + } + }); + } + + private static final Map INSTANCES = new WeakHashMap(); + + /** + * Get a instance of a multi user chat manager for the given connection. + * + * @param connection + * @return a multi user chat manager. + */ + public static synchronized MultiUserChatManager getInstanceFor(XMPPConnection connection) { + MultiUserChatManager multiUserChatManager = INSTANCES.get(connection); + if (multiUserChatManager == null) { + multiUserChatManager = new MultiUserChatManager(connection); + INSTANCES.put(connection, multiUserChatManager); + } + return multiUserChatManager; + } + + private static final PacketFilter invitationFilter = new AndFilter(new PacketExtensionFilter(new MUCUser()), + new NotFilter(MessageTypeFilter.ERROR)); + + private final Set invitationsListeners = new CopyOnWriteArraySet(); + private final Set joinedRooms = new HashSet(); + + /** + * A Map of MUC JIDs to {@link MultiUserChat} instances. We use weak references for the values in order to allow + * those instances to get garbage collected. Note that MultiUserChat instances can not get garbage collected while + * the user is joined, because then the MUC will have PacketListeners added to the XMPPConnection. + */ + private final Map> multiUserChats = new HashMap>(); + + private final PacketListener invitationPacketListener; + + private MultiUserChatManager(XMPPConnection connection) { + super(connection); + // Listens for all messages that include a MUCUser extension and fire the invitation + // listeners if the message includes an invitation. + invitationPacketListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + // Get the MUCUser extension + MUCUser mucUser = MUCUser.from(message); + // Check if the MUCUser extension includes an invitation + if (mucUser.getInvite() != null) { + // Fire event for invitation listeners + for (InvitationListener listener : invitationsListeners) { + listener.invitationReceived(connection(), packet.getFrom(), mucUser.getInvite().getFrom(), + mucUser.getInvite().getReason(), mucUser.getPassword(), message); + } + } + } + }; + connection.addPacketListener(invitationPacketListener, invitationFilter); + } + + /** + * Creates a multi user chat. Note: no information is sent to or received from the server until you attempt to + * {@link MultiUserChat#join(String) join} the chat room. On some server implementations, the room will not be + * created until the first person joins it. + *

+ * 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 jid 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 synchronized MultiUserChat getMultiUserChat(String jid) { + MultiUserChat multiUserChat = multiUserChats.get(jid).get(); + if (multiUserChat == null) { + multiUserChat = new MultiUserChat(connection(), jid, this); + multiUserChats.put(jid, new WeakReference(multiUserChat)); + } + return multiUserChat; + } + + /** + * Returns true if the specified user supports the Multi-User Chat protocol. + * + * @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. + * @throws XMPPErrorException + * @throws NoResponseException + * @throws NotConnectedException + */ + public boolean isServiceEnabled(String user) throws NoResponseException, XMPPErrorException, NotConnectedException { + return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(user, MUCInitialPresence.NAMESPACE); + } + + /** + * Returns a Set of the rooms where the user has joined. The Iterator will contain Strings where each String + * represents a room (e.g. room@muc.jabber.org). + * + * @return a List of the rooms where the user has joined using a given connection. + */ + public Set getJoinedRooms() { + return Collections.unmodifiableSet(joinedRooms); + } + + /** + * Returns a List of 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 user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. + * @return a List of the rooms where the requested user has joined. + * @throws XMPPErrorException + * @throws NoResponseException + * @throws NotConnectedException + */ + public List getJoinedRooms(String user) throws NoResponseException, XMPPErrorException, + NotConnectedException { + ArrayList answer = new ArrayList(); + // Send the disco packet to the user + DiscoverItems result = ServiceDiscoveryManager.getInstanceFor(connection()).discoverItems(user, DISCO_NODE); + // Collect the entityID for each returned item + for (DiscoverItems.Item item : result.getItems()) { + answer.add(item.getEntityID()); + } + return answer; + } + + /** + * 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 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 XMPPErrorException + * @throws NoResponseException + * @throws NotConnectedException + */ + public RoomInfo getRoomInfo(String room) throws NoResponseException, XMPPErrorException, NotConnectedException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection()).discoverInfo(room); + return new RoomInfo(info); + } + + /** + * Returns a collection with the XMPP addresses of the Multi-User Chat services. + * + * @return a collection with the XMPP addresses of the Multi-User Chat services. + * @throws XMPPErrorException + * @throws NoResponseException + * @throws NotConnectedException + */ + public List getServiceNames() throws NoResponseException, XMPPErrorException, NotConnectedException { + ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection()); + return sdm.findServices(MUCInitialPresence.NAMESPACE, false, false); + } + + /** + * 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 serviceName the service that is hosting the rooms to discover. + * @return a collection of HostedRooms. + * @throws XMPPErrorException + * @throws NoResponseException + * @throws NotConnectedException + */ + public Collection getHostedRooms(String serviceName) throws NoResponseException, XMPPErrorException, + NotConnectedException { + List answer = new ArrayList(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection()); + DiscoverItems items = discoManager.discoverItems(serviceName); + for (DiscoverItems.Item item : items.getItems()) { + answer.add(new HostedRoom(item)); + } + return answer; + } + + /** + * 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 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. + * @throws NotConnectedException + */ + public void decline(String room, String inviter, String reason) throws NotConnectedException { + 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); + + connection().sendPacket(message); + } + + /** + * Adds a listener to invitation notifications. The listener will be fired anytime an invitation is received. + * + * @param listener an invitation listener. + */ + public void addInvitationListener(InvitationListener listener) { + invitationsListeners.add(listener); + } + + /** + * Removes a listener to invitation notifications. The listener will be fired anytime an invitation is received. + * + * @param listener an invitation listener. + */ + public void removeInvitationListener(InvitationListener listener) { + invitationsListeners.remove(listener); + } + + void addJoinedRoom(String room) { + joinedRooms.add(room); + } + + void removeJoinedRoom(String room) { + joinedRooms.remove(room); + } +} diff --git a/smack-extensions/src/main/resources/org.jivesoftware.smackx/extensions.xml b/smack-extensions/src/main/resources/org.jivesoftware.smackx/extensions.xml index 5db58f683..dc28e7537 100644 --- a/smack-extensions/src/main/resources/org.jivesoftware.smackx/extensions.xml +++ b/smack-extensions/src/main/resources/org.jivesoftware.smackx/extensions.xml @@ -2,7 +2,7 @@ org.jivesoftware.smackx.disco.ServiceDiscoveryManager org.jivesoftware.smackx.xhtmlim.XHTMLManager - org.jivesoftware.smackx.muc.MultiUserChat + org.jivesoftware.smackx.muc.MultiUserChatManager org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager org.jivesoftware.smackx.filetransfer.FileTransferManager @@ -13,4 +13,4 @@ org.jivesoftware.smackx.time.EntityTimeManager org.jivesoftware.smackx.vcardtemp.VCardManager - \ No newline at end of file + diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/user/Workgroup.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/user/Workgroup.java index 831fc7ac0..418065432 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/user/Workgroup.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/user/Workgroup.java @@ -40,7 +40,7 @@ import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; -import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.muc.MultiUserChatManager; import org.jivesoftware.smackx.muc.packet.MUCUser; import org.jivesoftware.smackx.workgroup.MetaData; import org.jivesoftware.smackx.workgroup.WorkgroupInvitation; @@ -128,7 +128,7 @@ public class Workgroup { /** * Internal handling of an invitation.Recieving an invitation removes the user from the queue. */ - MultiUserChat.addInvitationListener(connection, + MultiUserChatManager.getInstanceFor(connection).addInvitationListener( new org.jivesoftware.smackx.muc.InvitationListener() { public void invitationReceived(XMPPConnection conn, String room, String inviter, String reason, String password, Message message) {