Add MucEnterConfiguration API

Allows to configure the presence used when joining a MUC.

Part of SMACK-648: Improve the MUC API.
This commit is contained in:
Florian Schmaus 2015-07-06 09:55:25 +02:00
parent bfdcfba092
commit 57cc3aa3f8
4 changed files with 369 additions and 48 deletions

View File

@ -39,7 +39,9 @@ import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
* Note: Setting maxchars to 0 indicates that the user requests to receive no history.
*
* @author Gaston Dombiak
* @deprecated use {@link org.jivesoftware.smackx.muc.MucEnterConfiguration} instead.
*/
@Deprecated
public class DiscussionHistory {
private int maxChars = -1;
@ -150,19 +152,6 @@ public class DiscussionHistory {
return null;
}
MUCInitialPresence.History mucHistory = new MUCInitialPresence.History();
if (maxChars > -1) {
mucHistory.setMaxChars(maxChars);
}
if (maxStanzas > -1) {
mucHistory.setMaxStanzas(maxStanzas);
}
if (seconds > -1) {
mucHistory.setSeconds(seconds);
}
if (since != null) {
mucHistory.setSince(since);
}
return mucHistory;
return new MUCInitialPresence.History(maxChars, maxStanzas, seconds, since);
}
}

View File

@ -0,0 +1,208 @@
/**
*
* Copyright 2015 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.util.Date;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
/**
* The configuration used to enter a MUC room. This configuration is usually used when joining an
* existing room. When creating a new room, only the Nickname setting is relevant.
*
* @author Florian Schmaus
* @since 4.2
*/
public final class MucEnterConfiguration {
private final Resourcepart nickname;
private final String password;
private final int maxChars;
private final int maxStanzas;
private final int seconds;
private final Date since;
private final long timeout;
private final Presence joinPresence;
MucEnterConfiguration(Builder builder) {
nickname = builder.nickname;
password = builder.password;
maxChars = builder.maxChars;
maxStanzas = builder.maxStanzas;
seconds = builder.seconds;
since = builder.since;
timeout = builder.timeout;
if (builder.joinPresence == null) {
joinPresence = new Presence(Presence.Type.available);
}
else {
joinPresence = builder.joinPresence.clone();
}
// Indicate the the client supports MUC
joinPresence.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds,
since));
}
Presence getJoinPresence(MultiUserChat multiUserChat) {
final EntityFullJid jid = JidCreate.fullFrom(multiUserChat.getRoom(), nickname);
joinPresence.setTo(jid);
return joinPresence;
}
long getTimeout() {
return timeout;
}
public static final class Builder {
private final Resourcepart nickname;
private String password;
private int maxChars = -1;
private int maxStanzas = -1;
private int seconds = -1;
private Date since;
private long timeout;
private Presence joinPresence;
Builder(Resourcepart nickname, long timeout) {
this.nickname = Objects.requireNonNull(nickname, "Nickname must not be null");
timeoutAfter(timeout);
}
/**
* Set the presence used to join the MUC room.
* <p>
* The 'to' value of the given presence will be overridden.
* <p>
*
* @param presence
* @return a reference to this builder.
*/
public Builder withPresence(Presence presence) {
if (presence.getType() == Presence.Type.available) {
throw new IllegalArgumentException();
}
joinPresence = presence;
return this;
}
/**
* Use the given password to join the MUC room.
*
* @param password the password used to join.
* @return a reference to this builder.
*/
public Builder withPassword(String password) {
this.password = password;
return this;
}
/**
* Set the timeout used when joining the MUC room.
*
* @param timeout the timeout to use when joining.
* @return a reference to this builder.
*/
public Builder timeoutAfter(long timeout) {
if (timeout <= 0) {
throw new IllegalArgumentException("timeout must be positive");
}
this.timeout = timeout;
return this;
}
/**
* Request that that MUC is going to sent us no history when joining.
*
* @return
* @return a reference to this builder.
*/
public Builder requestNoHistory() {
maxChars = 0;
maxStanzas = -1;
seconds = -1;
since = null;
return this;
}
/**
* Sets the total number of characters to receive in the history.
*
* @param maxChars the total number of characters to receive in the history.
* @return a reference to this builder.
*/
public Builder requestMaxCharsHistory(int maxChars) {
this.maxChars = maxChars;
return this;
}
/**
* Sets the total number of messages to receive in the history.
*
* @param maxStanzas the total number of messages to receive in the history.
* @return a reference to this builder.
*/
public Builder requestMaxStanzasHistory(int maxStanzas) {
this.maxStanzas = maxStanzas;
return this;
}
/**
* Sets the number of seconds to use to filter the messages received during that time.
* In other words, only the messages received in the last "X" seconds will be included in
* the history.
*
* @param seconds the number of seconds to use to filter the messages received during
* that time.
* @return a reference to this builder.
*/
public Builder requestHistorySince(int seconds) {
this.seconds = seconds;
return this;
}
/**
* Sets the since date to use to filter the messages received during that time.
* In other words, only the messages received since the datetime specified will be
* included in the history.
*
* @param since the since date to use to filter the messages received during that time.
* @return a reference to this builder.
*/
public Builder requestHistorySince(Date since) {
this.since = since;
return this;
}
/**
* Build a new {@link MucEnterConfiguration} with the current builder.
*
* @return a new {@code MucEnterConfiguration}.
*/
public MucEnterConfiguration build() {
return new MucEnterConfiguration(this);
}
}
}

View File

@ -284,10 +284,7 @@ public class MultiUserChat {
/**
* Enter a room, as described in XEP-45 7.2.
*
* @param nickname
* @param password
* @param history
* @param timeout
* @param conf the configuration used to enter the room.
* @return the returned presence by the service after the client send the initial presence in order to enter the room.
* @throws NotConnectedException
* @throws NoResponseException
@ -296,8 +293,7 @@ public class MultiUserChat {
* @throws NotAMucServiceException
* @see <a href="http://xmpp.org/extensions/xep-0045.html#enter">XEP-45 7.2 Entering a Room</a>
*/
private Presence enter(Resourcepart nickname, String password, DiscussionHistory history,
long timeout) throws NotConnectedException, NoResponseException,
private Presence enter(MucEnterConfiguration conf) throws NotConnectedException, NoResponseException,
XMPPErrorException, InterruptedException, NotAMucServiceException {
StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank.");
final DomainBareJid mucService = room.asDomainBareJid();
@ -310,19 +306,7 @@ public class MultiUserChat {
}
// We enter a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"
Presence joinPresence = new Presence(Presence.Type.available);
final EntityFullJid jid = JidCreate.fullFrom(room, nickname);
joinPresence.setTo(jid);
// 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);
Presence joinPresence = conf.getJoinPresence(this);
// Setup the messageListeners and presenceListeners *before* the join presence is send.
connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter);
@ -338,11 +322,11 @@ public class MultiUserChat {
// Wait for a presence packet back from the server.
// Use a bare JID filter, since the room may rewrite the nickname.
StanzaFilter responseFilter = new AndFilter(FromMatchesFilter.createBare(jid), new StanzaTypeFilter(
StanzaFilter responseFilter = new AndFilter(FromMatchesFilter.createBare(getRoom()), new StanzaTypeFilter(
Presence.class), MUCUserStatusCodeFilter.STATUS_110_PRESENCE_TO_SELF);
Presence presence;
try {
presence = connection.createPacketCollectorAndSend(responseFilter, joinPresence).nextResultOrThrow(timeout);
presence = connection.createPacketCollectorAndSend(responseFilter, joinPresence).nextResultOrThrow(conf.getTimeout());
}
catch (InterruptedException | NoResponseException | XMPPErrorException e) {
// Ensure that all callbacks are removed if there is an exception
@ -360,6 +344,17 @@ public class MultiUserChat {
return presence;
}
/**
* Get a new MUC enter configuration builder.
*
* @param nickname the nickname used when entering the MUC room.
* @return a new MUC enter configuration builder.
* @since 4.2
*/
public MucEnterConfiguration.Builder getEnterConfigurationBuilder(Resourcepart nickname) {
return new MucEnterConfiguration.Builder(nickname, connection.getPacketReplyTimeout());
}
/**
* 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
@ -419,7 +414,8 @@ public class MultiUserChat {
*/
public synchronized MucCreateConfigFormHandle createOrJoin(Resourcepart nickname) throws NoResponseException, XMPPErrorException,
InterruptedException, MucAlreadyJoinedException, NotConnectedException, NotAMucServiceException {
return createOrJoin(nickname, null, null, connection.getPacketReplyTimeout());
MucEnterConfiguration mucEnterConfiguration = getEnterConfigurationBuilder(nickname).build();
return createOrJoin(mucEnterConfiguration);
}
/**
@ -440,14 +436,40 @@ public class MultiUserChat {
* @throws MucAlreadyJoinedException if the MUC is already joined
* @throws NotConnectedException
* @throws NotAMucServiceException
* @deprecated use {@link #createOrJoin(MucEnterConfiguration)} instead.
*/
public synchronized MucCreateConfigFormHandle createOrJoin(Resourcepart nickname, String password, DiscussionHistory history, long timeout)
@Deprecated
public MucCreateConfigFormHandle createOrJoin(Resourcepart nickname, String password, DiscussionHistory history, long timeout)
throws NoResponseException, XMPPErrorException, InterruptedException, MucAlreadyJoinedException, NotConnectedException, NotAMucServiceException {
MucEnterConfiguration.Builder builder = getEnterConfigurationBuilder(nickname).withPassword(
password).timeoutAfter(timeout);
return createOrJoin(builder.build());
}
/**
* Like {@link #create(Resourcepart)}, 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 mucEnterConfiguration the configuration used to enter the MUC.
* @return A {@link MucCreateConfigFormHandle} if the room was created, or {code null} if the room was joined.
* @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 InterruptedException
* @throws MucAlreadyJoinedException if the MUC is already joined
* @throws NotConnectedException
* @throws NotAMucServiceException
*/
public synchronized MucCreateConfigFormHandle createOrJoin(MucEnterConfiguration mucEnterConfiguration)
throws NoResponseException, XMPPErrorException, InterruptedException, MucAlreadyJoinedException, NotConnectedException, NotAMucServiceException {
if (joined) {
throw new MucAlreadyJoinedException();
}
Presence presence = enter(nickname, password, history, timeout);
Presence presence = enter(mucEnterConfiguration);
// Look for confirmation of room creation from the server
MUCUser mucUser = MUCUser.from(presence);
@ -458,7 +480,6 @@ public class MultiUserChat {
return null;
}
/**
* A handle used to configure a newly created room. As long as the room is not configured it will be locked, which
* means that no one is able to join. The room will become unlocked as soon it got configured. In order to create an
@ -520,8 +541,10 @@ public class MultiUserChat {
if (isJoined()) {
return null;
}
MucEnterConfiguration mucEnterConfiguration = getEnterConfigurationBuilder(nickname).withPassword(
password).build();
try {
return createOrJoin(nickname, password, null, connection.getPacketReplyTimeout());
return createOrJoin(mucEnterConfiguration);
}
catch (MucAlreadyJoinedException e) {
return null;
@ -548,8 +571,10 @@ public class MultiUserChat {
* @throws InterruptedException
* @throws NotAMucServiceException
*/
public void join(Resourcepart nickname) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotAMucServiceException {
join(nickname, null, null, connection.getPacketReplyTimeout());
public void join(Resourcepart nickname) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotAMucServiceException {
MucEnterConfiguration.Builder builder = getEnterConfigurationBuilder(nickname);
join(builder.build());
}
/**
@ -576,7 +601,9 @@ public class MultiUserChat {
* @throws NotAMucServiceException
*/
public void join(Resourcepart nickname, String password) throws XMPPErrorException, InterruptedException, NoResponseException, NotConnectedException, NotAMucServiceException {
join(nickname, password, null, connection.getPacketReplyTimeout());
MucEnterConfiguration.Builder builder = getEnterConfigurationBuilder(nickname).withPassword(
password);
join(builder.build());
}
/**
@ -607,19 +634,55 @@ public class MultiUserChat {
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotAMucServiceException
* @deprecated use {@link #join(MucEnterConfiguration) instead.
*/
public synchronized void join(
@Deprecated
public void join(
Resourcepart nickname,
String password,
DiscussionHistory history,
long timeout)
throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException, NotAMucServiceException {
MucEnterConfiguration.Builder builder = getEnterConfigurationBuilder(nickname).withPassword(
password).timeoutAfter(timeout);
join(builder.build());
}
/**
* Joins the chat room using the specified nickname and password. If already joined
* using another nickname, this method will first leave the room and then
* re-join using the new nickname.<p>
*
* To control the amount of history to receive while joining a room you will need to provide
* a configured DiscussionHistory object.<p>
*
* A password is required when joining password protected rooms. If the room does
* not require a password there is no need to provide one.<p>
*
* If the room does not already exist when the user seeks to enter it, the server will
* decide to create a new room or not.
*
* @param mucEnterConfiguration the configuration used to enter the MUC.
* @throws XMPPErrorException if an error occurs joining the room. In particular, a
* 401 error can occur if no password was provided and one is required; or a
* 403 error can occur if the user is banned; or a
* 404 error can occur if the room does not exist or is locked; or a
* 407 error can occur if user is not on the member list; or a
* 409 error can occur if someone is already in the group chat with the same nickname.
* @throws NoResponseException if there was no response from the server.
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotAMucServiceException
*/
public synchronized void join(MucEnterConfiguration mucEnterConfiguration)
throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException, NotAMucServiceException {
// If we've already joined the room, leave it before joining under a new
// nickname.
if (joined) {
leave();
}
enter(nickname, password, history, timeout);
enter(mucEnterConfiguration);
}
/**

View File

@ -42,9 +42,36 @@ public class MUCInitialPresence implements ExtensionElement {
public static final String ELEMENT = "x";
public static final String NAMESPACE = "http://jabber.org/protocol/muc";
// TODO make those fields final once deprecated setter methods have been removed.
private String password;
private History history;
/**
* Deprecated constructor.
* @deprecated use {@link #MUCInitialPresence(String, int, int, int, Date)} instead.
*/
@Deprecated
public MUCInitialPresence() {
}
/**
* Construct a new MUC initial presence extension.
*
* @param password the optional password used to enter the room.
* @param maxChars the maximal count of characters of history to request.
* @param maxStanzas the maximal count of stanzas of history to request.
* @param seconds the last seconds since when to request history.
* @param since the date since when to request history.
*/
public MUCInitialPresence(String password, int maxChars, int maxStanzas, int seconds, Date since) {
this.password = password;
if (maxChars > -1 || maxStanzas > -1 || seconds > -1 || since != null) {
this.history = new History(maxChars, maxStanzas, seconds, since);
} else {
this.history = null;
}
}
public String getElementName() {
return ELEMENT;
}
@ -89,7 +116,9 @@ public class MUCInitialPresence implements ExtensionElement {
*
* @param history that manages the amount of discussion history provided on
* entering a room.
* @deprecated use {@link #MUCInitialPresence(String, int, int, int, Date)} instead.
*/
@Deprecated
public void setHistory(History history) {
this.history = history;
}
@ -98,7 +127,9 @@ public class MUCInitialPresence implements ExtensionElement {
* Sets the password to use when the room requires a password.
*
* @param password the password to use when the room requires a password.
* @deprecated use {@link #MUCInitialPresence(String, int, int, int, Date)} instead.
*/
@Deprecated
public void setPassword(String password) {
this.password = password;
}
@ -135,11 +166,33 @@ public class MUCInitialPresence implements ExtensionElement {
public static final String ELEMENT = "history";
private int maxChars = -1;
private int maxStanzas = -1;
private int seconds = -1;
// TODO make those fields final once the deprecated setter methods have been removed.
private int maxChars;
private int maxStanzas;
private int seconds;
private Date since;
/**
* Deprecated constructor.
* @deprecated use {@link #MUCInitialPresence.History(int, int, int, Date)} instead.
*/
@Deprecated
public History() {
this.maxChars = -1;
this.maxStanzas = -1;
this.seconds = -1;
}
public History(int maxChars, int maxStanzas, int seconds, Date since) {
if (maxChars < 0 && maxStanzas < 0 && seconds < 0 && since == null) {
throw new IllegalArgumentException();
}
this.maxChars = maxChars;
this.maxStanzas = maxStanzas;
this.seconds = seconds;
this.since = since;
}
/**
* Returns the total number of characters to receive in the history.
*
@ -184,7 +237,9 @@ public class MUCInitialPresence implements ExtensionElement {
* Sets the total number of characters to receive in the history.
*
* @param maxChars the total number of characters to receive in the history.
* @deprecated use {@link #MUCInitialPresence.History(int, int, int, Date)} instead.
*/
@Deprecated
public void setMaxChars(int maxChars) {
this.maxChars = maxChars;
}
@ -193,7 +248,9 @@ public class MUCInitialPresence implements ExtensionElement {
* Sets the total number of messages to receive in the history.
*
* @param maxStanzas the total number of messages to receive in the history.
* @deprecated use {@link #MUCInitialPresence.History(int, int, int, Date)} instead.
*/
@Deprecated
public void setMaxStanzas(int maxStanzas) {
this.maxStanzas = maxStanzas;
}
@ -205,7 +262,9 @@ public class MUCInitialPresence implements ExtensionElement {
*
* @param seconds the number of seconds to use to filter the messages received during
* that time.
* @deprecated use {@link #MUCInitialPresence.History(int, int, int, Date)} instead.
*/
@Deprecated
public void setSeconds(int seconds) {
this.seconds = seconds;
}
@ -216,7 +275,9 @@ public class MUCInitialPresence implements ExtensionElement {
* included in the history.
*
* @param since the since date to use to filter the messages received during that time.
* @deprecated use {@link #MUCInitialPresence.History(int, int, int, Date)} instead.
*/
@Deprecated
public void setSince(Date since) {
this.since = since;
}