diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/DiscussionHistory.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/DiscussionHistory.java index 35d6600e5..c9a2c77c7 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/DiscussionHistory.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/DiscussionHistory.java @@ -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); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java new file mode 100644 index 000000000..02057a763 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java @@ -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. + *

+ * The 'to' value of the given presence will be overridden. + *

+ * + * @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); + } + + } +} 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 88f316069..95bf7e816 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 @@ -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 XEP-45 7.2 Entering a Room */ - 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.

+ * + * To control the amount of history to receive while joining a room you will need to provide + * a configured DiscussionHistory object.

+ * + * A password is required when joining password protected rooms. If the room does + * not require a password there is no need to provide one.

+ * + * 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); } /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/packet/MUCInitialPresence.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/packet/MUCInitialPresence.java index 99ac34959..6482971fb 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/packet/MUCInitialPresence.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/packet/MUCInitialPresence.java @@ -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; }