From b54d133b365d7a8910127893d8f636daf364b000 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 13 Oct 2014 22:13:43 +0200 Subject: [PATCH] Add MultiUserChatManager apply the Manager pattern to 'muc'. This prevents the user creating multiple MultiUserChat instances for the same MUC. Move the static method from MultiUserChat to MultiUserChatManager. Also add AbstractNodeInformationProvider. --- documentation/extensions/muc.md | 114 +++--- .../AbstractNodeInformationProvider.java | 47 +++ .../jivesoftware/smackx/muc/HostedRoom.java | 4 +- .../smackx/muc/MultiUserChat.java | 335 +----------------- .../smackx/muc/MultiUserChatManager.java | 305 ++++++++++++++++ .../org.jivesoftware.smackx/extensions.xml | 4 +- .../smackx/workgroup/user/Workgroup.java | 4 +- 7 files changed, 438 insertions(+), 375 deletions(-) create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/disco/AbstractNodeInformationProvider.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java 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) {