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.
This commit is contained in:
Florian Schmaus 2014-10-13 22:13:43 +02:00
parent 0ef50bfd1e
commit b54d133b36
7 changed files with 438 additions and 375 deletions

View File

@ -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<String> 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() {

View File

@ -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<DiscoverItems.Item> getNodeItems() {
return null;
}
public List<String> getNodeFeatures() {
return null;
}
public List<DiscoverInfo.Identity> getNodeIdentities() {
return null;
}
public List<PacketExtension> getNodePacketExtensions() {
return null;
}
}

View File

@ -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

View File

@ -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<XMPPConnection, List<String>> joinedRooms =
new WeakHashMap<XMPPConnection, List<String>>();
private final XMPPConnection connection;
private final String room;
private final MultiUserChatManager multiUserChatManager;
private final Map<String, Presence> occupantsMap = new ConcurrentHashMap<String, Presence>();
private final Set<InvitationRejectionListener> invitationRejectionListeners = new CopyOnWriteArraySet<InvitationRejectionListener>();
@ -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<XMPPConnection> weakRefConnection = new WeakReference<XMPPConnection>(connection);
ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(
DISCO_NODE,
new NodeInformationProvider() {
public List<DiscoverItems.Item> getNodeItems() {
XMPPConnection connection = weakRefConnection.get();
if (connection == null) return Collections.emptyList();
List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>();
for (String room : MultiUserChat.getJoinedRooms(connection)) {
answer.add(new DiscoverItems.Item(room));
}
return answer;
}
public List<String> getNodeFeatures() {
return null;
}
public List<DiscoverInfo.Identity> getNodeIdentities() {
return null;
}
@Override
public List<PacketExtension> getNodePacketExtensions() {
return null;
}
});
}
});
}
/**
* Creates a new multi user chat with the specified connection and room name. Note: no
* information is sent to or received from the server until you attempt to
* {@link #join(String) join} the chat room. On some server implementations,
* the room will not be created until the first person joins it.<p>
*
* Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
* for the XMPP server example.com). You must ensure that the room address you're
* trying to connect to includes the proper chat sub-domain.
*
* @param connection the XMPP connection.
* @param room the name of the room in the form "roomName@service", where
* "service" is the hostname at which the multi-user chat
* service is running. Make sure to provide a valid JID.
*/
public MultiUserChat(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<String> getJoinedRooms(XMPPConnection connection) {
List<String> 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<String> getJoinedRooms(XMPPConnection connection, String user)
throws NoResponseException, XMPPErrorException, NotConnectedException {
ArrayList<String> answer = new ArrayList<String>();
// 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<String> 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<HostedRoom> getHostedRooms(XMPPConnection connection,
String serviceName) throws NoResponseException, XMPPErrorException, NotConnectedException {
List<HostedRoom> answer = new ArrayList<HostedRoom>();
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<String> rooms = joinedRooms.get(connection);
if (rooms == null) {
rooms = new ArrayList<String>();
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<String> 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<XMPPConnection, InvitationsMonitor> INSTANCES = new WeakHashMap<XMPPConnection, InvitationsMonitor>();
private static final PacketFilter invitationFilter = new PacketExtensionFilter(new MUCUser());
private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
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);
}
}
}
}

View File

@ -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<XMPPConnection> weakRefConnection = new WeakReference<XMPPConnection>(connection);
ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(DISCO_NODE,
new AbstractNodeInformationProvider() {
@Override
public List<DiscoverItems.Item> getNodeItems() {
XMPPConnection connection = weakRefConnection.get();
if (connection == null)
return Collections.emptyList();
Set<String> joinedRooms = MultiUserChatManager.getInstanceFor(connection).getJoinedRooms();
List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>();
for (String room : joinedRooms) {
answer.add(new DiscoverItems.Item(room));
}
return answer;
}
});
}
});
}
private static final Map<XMPPConnection, MultiUserChatManager> INSTANCES = new WeakHashMap<XMPPConnection, MultiUserChatManager>();
/**
* 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<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
private final Set<String> joinedRooms = new HashSet<String>();
/**
* 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<String, WeakReference<MultiUserChat>> multiUserChats = new HashMap<String, WeakReference<MultiUserChat>>();
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.
* <p>
* Most XMPP servers use a sub-domain for the chat service (eg chat.example.com for the XMPP server example.com).
* You must ensure that the room address you're trying to connect to includes the proper chat sub-domain.
* </p>
*
* @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>(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<String> 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<String> getJoinedRooms(String user) throws NoResponseException, XMPPErrorException,
NotConnectedException {
ArrayList<String> answer = new ArrayList<String>();
// 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<String> 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<HostedRoom> getHostedRooms(String serviceName) throws NoResponseException, XMPPErrorException,
NotConnectedException {
List<HostedRoom> answer = new ArrayList<HostedRoom>();
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);
}
}

View File

@ -2,7 +2,7 @@
<startupClasses>
<className>org.jivesoftware.smackx.disco.ServiceDiscoveryManager</className>
<className>org.jivesoftware.smackx.xhtmlim.XHTMLManager</className>
<className>org.jivesoftware.smackx.muc.MultiUserChat</className>
<className>org.jivesoftware.smackx.muc.MultiUserChatManager</className>
<className>org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager</className>
<className>org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager</className>
<className>org.jivesoftware.smackx.filetransfer.FileTransferManager</className>
@ -13,4 +13,4 @@
<className>org.jivesoftware.smackx.time.EntityTimeManager</className>
<className>org.jivesoftware.smackx.vcardtemp.VCardManager</className>
</startupClasses>
</smack>
</smack>

View File

@ -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) {