Provide a MUC method to create *or* join a room

MulitUserChat.create() will throw an SmackException if the MUC service
does not return a 201 status when entering a room. Some MUC
implementations don't return the 201 status but instead behave like
the room already existed.

If the user doesn't care about the room beeing locked until the
initial configuration has been send, he can now use the new
MutliUserChat.createOrJoin(String) method.

Also remove some duplicate code by creating the private enter() method.

Fixes SMACK-557
This commit is contained in:
Florian Schmaus 2014-04-15 23:33:42 +02:00
parent 8ba0715cc3
commit bd5ceded37
2 changed files with 116 additions and 99 deletions

View File

@ -513,17 +513,27 @@ public class StringUtils {
} }
/** /**
* Returns true if string is not null and is not empty, false otherwise * Returns true if CharSequence is not null and is not empty, false otherwise
* Examples: * Examples:
* isNotEmpty(null) - false * isNotEmpty(null) - false
* isNotEmpty("") - false * isNotEmpty("") - false
* isNotEmpty(" ") - true * isNotEmpty(" ") - true
* isNotEmpty("empty") - true * isNotEmpty("empty") - true
* *
* @param string checked String * @param cs checked CharSequence
* @return true if string is not null and is not empty, false otherwise * @return true if string is not null and is not empty, false otherwise
*/ */
public static boolean isNotEmpty(CharSequence string) { public static boolean isNotEmpty(CharSequence cs) {
return string != null && string.length() != 0; return !isNullOrEmpty(cs);
}
/**
* Returns true if the given CharSequence is not null or empty.
*
* @param cs
* @return true if the given CharSequence is not null or empty
*/
public static boolean isNullOrEmpty(CharSequence cs) {
return cs == null || cs.length() == 0;
} }
} }

View File

@ -58,6 +58,7 @@ import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Registration; import org.jivesoftware.smack.packet.Registration;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.disco.NodeInformationProvider; import org.jivesoftware.smackx.disco.NodeInformationProvider;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
@ -302,74 +303,130 @@ public class MultiUserChat {
} }
/** /**
* Creates the room according to some default configuration, assign the requesting user * Enter a room, as described in XEP-45 7.2.
* as the room owner, and add the owner to the room but not allow anyone else to enter
* the room (effectively "locking" the room). The requesting user will join the room
* under the specified nickname as soon as the room has been created.<p>
* *
* To create an "Instant Room", that means a room with some default configuration that is * @param nickname
* available for immediate access, the room's owner should send an empty form after creating * @param password
* the room. {@link #sendConfigurationForm(Form)}<p> * @param history
* * @param timeout
* To create a "Reserved Room", that means a room manually configured by the room creator * @return the returned presence by the service after the client send the initial presence in order to enter the room.
* before anyone is allowed to enter, the room's owner should complete and send a form after * @throws NotConnectedException
* creating the room. Once the completed configutation form is sent to the server, the server * @throws NoResponseException
* will unlock the room. {@link #sendConfigurationForm(Form)} * @throws XMPPErrorException
* * @see <a href="http://xmpp.org/extensions/xep-0045.html#enter">XEP-45 7.2 Entering a Room</a>
* @param nickname the nickname to use.
* @throws XMPPErrorException if the room couldn't be created for some reason
* (e.g. room already exists; user already joined to an existant room or
* 405 error if the user is not allowed to create the room)
* @throws NoResponseException if there was no response from the server.
* @throws SmackException If the creation failed because of a missing acknowledge from the server.
*/ */
public synchronized void create(String nickname) throws NoResponseException, XMPPErrorException, SmackException { private Presence enter(String nickname, String password, DiscussionHistory history,
if (nickname == null || nickname.equals("")) { long timeout) throws NotConnectedException, NoResponseException,
XMPPErrorException {
if (StringUtils.isNullOrEmpty(nickname)) {
throw new IllegalArgumentException("Nickname must not be null or blank."); throw new IllegalArgumentException("Nickname must not be null or blank.");
} }
// If we've already joined the room, leave it before joining under a new // We enter a room by sending a presence packet where the "to"
// nickname. // field is in the form "roomName@service/nickname"
if (joined) {
throw new IllegalStateException("Creation failed - User already joined the room.");
}
// We create a room by sending a presence packet to room@service/nick
// and signal support for MUC. The owner will be automatically logged into the room.
Presence joinPresence = new Presence(Presence.Type.available); Presence joinPresence = new Presence(Presence.Type.available);
joinPresence.setTo(room + "/" + nickname); joinPresence.setTo(room + "/" + nickname);
// Indicate the the client supports MUC // Indicate the the client supports MUC
joinPresence.addExtension(new MUCInitialPresence()); MUCInitialPresence mucInitialPresence = new MUCInitialPresence();
if (password != null) {
mucInitialPresence.setPassword(password);
}
if (history != null) {
mucInitialPresence.setHistory(history.getMUCHistory());
}
joinPresence.addExtension(mucInitialPresence);
// Invoke presence interceptors so that extra information can be dynamically added // Invoke presence interceptors so that extra information can be dynamically added
for (PacketInterceptor packetInterceptor : presenceInterceptors) { for (PacketInterceptor packetInterceptor : presenceInterceptors) {
packetInterceptor.interceptPacket(joinPresence); packetInterceptor.interceptPacket(joinPresence);
} }
// Wait for a presence packet back from the server. // Wait for a presence packet back from the server.
PacketFilter responseFilter = PacketFilter responseFilter = new AndFilter(FromMatchesFilter.createFull(room + "/"
new AndFilter( + nickname), new PacketTypeFilter(Presence.class));
FromMatchesFilter.createFull(room + "/" + nickname), PacketCollector response = null;
new PacketTypeFilter(Presence.class));
PacketCollector response = connection.createPacketCollector(responseFilter); response = connection.createPacketCollector(responseFilter);
// Send create & join packet. // Send join packet.
connection.sendPacket(joinPresence); connection.sendPacket(joinPresence);
// Wait up to a certain number of seconds for a reply. // Wait up to a certain number of seconds for a reply.
Presence presence = (Presence) response.nextResultOrThrow(); Presence presence = (Presence) response.nextResultOrThrow(timeout);
// Whether the room existed before or was created, the user has joined the room
this.nickname = nickname; this.nickname = nickname;
joined = true; joined = true;
userHasJoined(); // Update the list of joined rooms through this connection
List<String> rooms = joinedRooms.get(connection);
if (rooms == null) {
rooms = new ArrayList<String>();
joinedRooms.put(connection, rooms);
}
rooms.add(room);
return presence;
}
/**
* Creates the room according to some default configuration, assign the requesting user as the
* room owner, and add the owner to the room but not allow anyone else to enter the room
* (effectively "locking" the room). The requesting user will join the room under the specified
* nickname as soon as the room has been created.
* <p>
* To create an "Instant Room", that means a room with some default configuration that is
* available for immediate access, the room's owner should send an empty form after creating the
* room. {@link #sendConfigurationForm(Form)}
* <p>
* To create a "Reserved Room", that means a room manually configured by the room creator before
* anyone is allowed to enter, the room's owner should complete and send a form after creating
* the room. Once the completed configuration form is sent to the server, the server will unlock
* the room. {@link #sendConfigurationForm(Form)}
*
* @param nickname the nickname to use.
* @throws XMPPErrorException if the room couldn't be created for some reason (e.g. 405 error if
* the user is not allowed to create the room)
* @throws NoResponseException if there was no response from the server.
* @throws SmackException If the creation failed because of a missing acknowledge from the
* server, e.g. because the room already existed.
*/
public synchronized void create(String nickname) throws NoResponseException, XMPPErrorException, SmackException {
if (joined) {
throw new IllegalStateException("Creation failed - User already joined the room.");
}
if (createOrJoin(nickname)) {
// We successfully created a new room
return;
}
// We need to leave the room since it seems that the room already existed
leave();
throw new SmackException("Creation failed - Missing acknowledge of room creation.");
}
/**
* Like {@link #create(String)}, but will return true if the room creation was acknowledged by
* the service (with an 201 status code). It's up to the caller to decide, based on the return
* value, if he needs to continue sending the room configuration. If false is returned, the room
* already existed and the user is able to join right away, without sending a form.
*
* @param nickname the nickname to use.
* @return true if the room creation was acknowledged by the service, false otherwise.
* @throws XMPPErrorException if the room couldn't be created for some reason (e.g. 405 error if
* the user is not allowed to create the room)
* @throws NoResponseException if there was no response from the server.
*/
public synchronized boolean createOrJoin(String nickname) throws NoResponseException, XMPPErrorException, SmackException {
if (joined) {
throw new IllegalStateException("Creation failed - User already joined the room.");
}
Presence presence = enter(nickname, null, null, connection.getPacketReplyTimeout());
// Look for confirmation of room creation from the server // Look for confirmation of room creation from the server
MUCUser mucUser = getMUCUserExtension(presence); MUCUser mucUser = getMUCUserExtension(presence);
if (mucUser != null && mucUser.getStatus() != null) { if (mucUser != null && mucUser.getStatus() != null) {
if ("201".equals(mucUser.getStatus().getCode())) { if ("201".equals(mucUser.getStatus().getCode())) {
// Room was created and the user has joined the room // Room was created and the user has joined the room
return; return true;
} }
} }
// We need to leave the room since it seems that the room already existed return false;
leave();
throw new SmackException("Creation failed - Missing acknowledge of room creation.");
} }
/** /**
@ -451,49 +508,12 @@ public class MultiUserChat {
DiscussionHistory history, DiscussionHistory history,
long timeout) long timeout)
throws XMPPErrorException, NoResponseException, NotConnectedException { throws XMPPErrorException, NoResponseException, NotConnectedException {
if (nickname == null || nickname.equals("")) {
throw new IllegalArgumentException("Nickname must not be null or blank.");
}
// If we've already joined the room, leave it before joining under a new // If we've already joined the room, leave it before joining under a new
// nickname. // nickname.
if (joined) { if (joined) {
leave(); leave();
} }
// We join a room by sending a presence packet where the "to" enter(nickname, password, history, timeout);
// field is in the form "roomName@service/nickname"
Presence joinPresence = new Presence(Presence.Type.available);
joinPresence.setTo(room + "/" + nickname);
// Indicate the the client supports MUC
MUCInitialPresence mucInitialPresence = new MUCInitialPresence();
if (password != null) {
mucInitialPresence.setPassword(password);
}
if (history != null) {
mucInitialPresence.setHistory(history.getMUCHistory());
}
joinPresence.addExtension(mucInitialPresence);
// Invoke presence interceptors so that extra information can be dynamically added
for (PacketInterceptor packetInterceptor : presenceInterceptors) {
packetInterceptor.interceptPacket(joinPresence);
}
// Wait for a presence packet back from the server.
PacketFilter responseFilter =
new AndFilter(
FromMatchesFilter.createFull(room + "/" + nickname),
new PacketTypeFilter(Presence.class));
PacketCollector response = null;
response = connection.createPacketCollector(responseFilter);
// Send join packet.
connection.sendPacket(joinPresence);
// Wait up to a certain number of seconds for a reply.
response.nextResultOrThrow(timeout);
this.nickname = nickname;
joined = true;
userHasJoined();
} }
/** /**
@ -916,7 +936,7 @@ public class MultiUserChat {
* @throws NotConnectedException * @throws NotConnectedException
*/ */
public void changeNickname(String nickname) throws NoResponseException, XMPPErrorException, NotConnectedException { public void changeNickname(String nickname) throws NoResponseException, XMPPErrorException, NotConnectedException {
if (nickname == null || nickname.equals("")) { if (StringUtils.isNullOrEmpty(nickname)) {
throw new IllegalArgumentException("Nickname must not be null or blank."); throw new IllegalArgumentException("Nickname must not be null or blank.");
} }
// Check that we already have joined the room before attempting to change the // Check that we already have joined the room before attempting to change the
@ -959,7 +979,7 @@ public class MultiUserChat {
* @throws NotConnectedException * @throws NotConnectedException
*/ */
public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException { public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException {
if (nickname == null || nickname.equals("")) { if (StringUtils.isNullOrEmpty(nickname)) {
throw new IllegalArgumentException("Nickname must not be null or blank."); throw new IllegalArgumentException("Nickname must not be null or blank.");
} }
// Check that we already have joined the room before attempting to change the // Check that we already have joined the room before attempting to change the
@ -1794,19 +1814,6 @@ public class MultiUserChat {
response.nextResultOrThrow(); response.nextResultOrThrow();
} }
/**
* Notification message that the user has joined the room.
*/
private synchronized void userHasJoined() {
// Update the list of joined rooms through this connection
List<String> rooms = joinedRooms.get(connection);
if (rooms == null) {
rooms = new ArrayList<String>();
joinedRooms.put(connection, rooms);
}
rooms.add(room);
}
/** /**
* Notification message that the user has left the room. * Notification message that the user has left the room.
*/ */