Add MucBookmarkAutojoinManager

Also add MucConfigFormManager and improve the MUC API (SMACK-648). Bump
to jxmpp 0.5.0-alpha3.

Improve and extend PrivateDataManager and BookmarkManager.
This commit is contained in:
Florian Schmaus 2015-04-21 19:05:22 +02:00
parent 265e5c69d5
commit f274581c27
15 changed files with 690 additions and 102 deletions

View File

@ -38,8 +38,8 @@ the _**MultiUserChat**_ instance where nickname is the nickname to use when
joining the room.
Depending on the type of room that you want to create you will have to use
different configuration forms. In order to create an Instant room just send
**sendConfigurationForm(Form form)** where form is an empty form. But if you
different configuration forms. In order to create an Instant room just use
`MucCreateConfigFormHandle.makeInstant()`. But if you
want to create a Reserved room then you should first get the room's
configuration form, complete the form and finally send it back to the server.
@ -54,12 +54,8 @@ MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
// Get a MultiUserChat using MultiUserChatManager
MultiUserChat muc = manager.getMultiUserChat("myroom@conference.jabber.org");
// Create the room
muc.create("testbot");
// Send an empty room configuration form which indicates that we want
// an instant room
muc.sendConfigurationForm(new Form(DataForm.Type.submit));
// Create the room and send an empty configuration form to make this an instant room
muc.create("testbot").makeInstant();
```
In this example we can see how to create a reserved room. The form is
@ -72,27 +68,14 @@ MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
// Create a MultiUserChat using an XMPPConnection for a room
MultiUserChat muc = = manager.getMultiUserChat("myroom@conference.jabber.org");
// Create the room
muc.create("testbot");
// Prepare a list of owners of the new room
Set<Jid> owners = JidUtil.jidSetFrom(new String[] { "me@example.org", "juliet@example.org" });
// Get the the room's configuration form
Form form = muc.getConfigurationForm();
// Create a new form to submit based on the original form
Form submitForm = form.createAnswerForm();
// Add default answers to the form to submit
for (Iterator fields = form.getFields(); fields.hasNext();) {
FormField field = (FormField) fields.next();
if (!FormField.type.hidden.equals(field.getType()) && field.getVariable() != null) {
// Sets the default value as the answer
submitForm.setDefaultAnswer(field.getVariable());
}
}
// Sets the new owner of the room
List owners = new ArrayList();
owners.add("johndoe@jabber.org");
submitForm.setAnswer("muc#roomconfig_roomowners", owners);
// Send the completed form (with default values) to the server to configure the room
muc.sendConfigurationForm(submitForm);
// Create the room
muc.create("testbot")
.getConfigFormManger()
.setRoomOwners(owners)
.submitConfigurationForm();
```
Join a room

View File

@ -28,6 +28,8 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smackx.iqprivate.PrivateDataManager;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.parts.Resourcepart;
/**
@ -109,8 +111,8 @@ public final class BookmarkManager {
* @throws NotConnectedException
* @throws InterruptedException
*/
public void addBookmarkedConference(String name, String jid, boolean isAutoJoin,
String nickname, String password) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
public void addBookmarkedConference(String name, BareJid jid, boolean isAutoJoin,
Resourcepart nickname, String password) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
retrieveBookmarks();
BookmarkedConference bookmark
@ -144,12 +146,12 @@ public final class BookmarkManager {
* @throws IllegalArgumentException thrown when the conference being removed is a shared
* conference
*/
public void removeBookmarkedConference(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
public void removeBookmarkedConference(BareJid jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
retrieveBookmarks();
Iterator<BookmarkedConference> it = bookmarks.getBookmarkedConferences().iterator();
while(it.hasNext()) {
BookmarkedConference conference = it.next();
if(conference.getJid().equalsIgnoreCase(jid)) {
if(conference.getJid().equals(jid)) {
if(conference.isShared()) {
throw new IllegalArgumentException("Conference is shared and can't be removed");
}
@ -230,6 +232,22 @@ public final class BookmarkManager {
}
}
/**
* Check if the service supports bookmarks using private data.
*
* @return true if the service supports private data, false otherwise.
* @throws NoResponseException
* @throws NotConnectedException
* @throws InterruptedException
* @throws XMPPErrorException
* @see PrivateDataManager#isSupported()
* @since 4.2
*/
public boolean isSupported() throws NoResponseException, NotConnectedException,
XMPPErrorException, InterruptedException {
return privateDataManager.isSupported();
}
private Bookmarks retrieveBookmarks() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
synchronized(bookmarkLock) {
if(bookmarks == null) {

View File

@ -17,6 +17,9 @@
package org.jivesoftware.smackx.bookmarks;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.parts.Resourcepart;
/**
* Respresents a Conference Room bookmarked on the server using XEP-0048 Bookmark Storage XEP.
*
@ -26,17 +29,17 @@ public class BookmarkedConference implements SharedBookmark {
private String name;
private boolean autoJoin;
private final String jid;
private final BareJid jid;
private String nickname;
private Resourcepart nickname;
private String password;
private boolean isShared;
protected BookmarkedConference(String jid) {
protected BookmarkedConference(BareJid jid) {
this.jid = jid;
}
protected BookmarkedConference(String name, String jid, boolean autoJoin, String nickname,
protected BookmarkedConference(String name, BareJid jid, boolean autoJoin, Resourcepart nickname,
String password)
{
this.name = name;
@ -78,7 +81,7 @@ public class BookmarkedConference implements SharedBookmark {
*
* @return the full JID of this conference room.
*/
public String getJid() {
public BareJid getJid() {
return jid;
}
@ -88,11 +91,11 @@ public class BookmarkedConference implements SharedBookmark {
*
* @return the nickname to use when joining, null may be returned.
*/
public String getNickname() {
public Resourcepart getNickname() {
return nickname;
}
protected void setNickname(String nickname) {
protected void setNickname(Resourcepart nickname) {
this.nickname = nickname;
}
@ -116,7 +119,7 @@ public class BookmarkedConference implements SharedBookmark {
return false;
}
BookmarkedConference conference = (BookmarkedConference)obj;
return conference.getJid().equalsIgnoreCase(jid);
return conference.getJid().equals(jid);
}
@Override

View File

@ -16,9 +16,12 @@
*/
package org.jivesoftware.smackx.bookmarks;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.iqprivate.packet.PrivateData;
import org.jivesoftware.smackx.iqprivate.provider.PrivateDataProvider;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.parts.Resourcepart;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@ -268,7 +271,7 @@ public class Bookmarks implements PrivateData {
private static BookmarkedConference getConferenceStorage(XmlPullParser parser) throws XmlPullParserException, IOException {
String name = parser.getAttributeValue("", "name");
String autojoin = parser.getAttributeValue("", "autojoin");
String jid = parser.getAttributeValue("", "jid");
BareJid jid = ParserUtils.getBareJidAttribute(parser);
BookmarkedConference conf = new BookmarkedConference(jid);
conf.setName(name);
@ -279,7 +282,8 @@ public class Bookmarks implements PrivateData {
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG && "nick".equals(parser.getName())) {
conf.setNickname(parser.nextText());
String nickString = parser.nextText();
conf.setNickname(Resourcepart.from(nickString));
}
else if (eventType == XmlPullParser.START_TAG && "password".equals(parser.getName())) {
conf.setPassword(parser.nextText());

View File

@ -24,6 +24,7 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.XMPPError.Condition;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smackx.iqprivate.packet.DefaultPrivateData;
import org.jivesoftware.smackx.iqprivate.packet.PrivateData;
@ -184,6 +185,51 @@ public final class PrivateDataManager extends Manager {
connection().createPacketCollectorAndSend(privateDataSet).nextResultOrThrow();
}
private static final PrivateData DUMMY_PRIVATE_DATA = new PrivateData() {
@Override
public String getElementName() {
return "smackDummyPrivateData";
}
@Override
public String getNamespace() {
return "https://igniterealtime.org/projects/smack/";
}
@Override
public CharSequence toXML() {
return '<' + getElementName() + " xmlns='" + getNamespace() + "'/>";
}
};
/**
* Check if the service supports private data.
*
* @return true if the service supports private data, false otherwise.
* @throws NoResponseException
* @throws NotConnectedException
* @throws InterruptedException
* @throws XMPPErrorException
* @since 4.2
*/
public boolean isSupported() throws NoResponseException, NotConnectedException,
InterruptedException, XMPPErrorException {
// This is just a primitive hack, since XEP-49 does not specify a way to determine if the
// service supports it
try {
setPrivateData(DUMMY_PRIVATE_DATA);
return true;
}
catch (XMPPErrorException e) {
if (e.getXMPPError().getCondition() == Condition.service_unavailable) {
return false;
}
else {
throw e;
}
}
}
/**
* An IQ provider to parse IQ results containing private data.
*/

View File

@ -0,0 +1,116 @@
/**
*
* 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.ArrayList;
import java.util.Collection;
import java.util.List;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.muc.MultiUserChatException.MucConfigurationNotSupported;
import org.jivesoftware.smackx.xdata.Form;
import org.jivesoftware.smackx.xdata.FormField;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.util.JidUtil;
/**
* Multi-User Chat configuration form manager is used to fill out and submit a {@link Form} used to
* configure rooms.
* <p>
* Room configuration needs either be done right after the room is created and still locked. Or at
* any later point (see <a href="http://xmpp.org/extensions/xep-0045.html#roomconfig">XEP-45 § 10.2
* Subsequent Room Configuration</a>). When done with the configuration, call
* {@link #submitConfigurationForm()}.
* </p>
* <p>
* The manager may not provide all possible configuration options. If you want direct access to the
* configuraiton form, use {@link MultiUserChat#getConfigurationForm()} and
* {@link MultiUserChat#sendConfigurationForm(Form)}.
* </p>
*/
public class MucConfigFormManager {
public static final String MUC_ROOMCONFIG_ROOMOWNERS = "muc#roomconfig_roomowners";
private final MultiUserChat multiUserChat;
private final Form answerForm;
private final List<Jid> owners;
/**
* Create a new MUC config form manager.
* <p>
* Note that the answerForm needs to be filled out with the defaults.
* </p>
*
* @param multiUserChat the MUC for this configuration form.
* @throws InterruptedException
* @throws NotConnectedException
* @throws XMPPErrorException
* @throws NoResponseException
*/
MucConfigFormManager(MultiUserChat multiUserChat) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
this.multiUserChat = multiUserChat;
// Set the answer form
Form configForm = multiUserChat.getConfigurationForm();
this.answerForm = configForm.createAnswerForm();
// Add the default answers to the form to submit
for (FormField field : configForm.getFields()) {
if (field.getType() == FormField.Type.hidden
|| StringUtils.isNullOrEmpty(field.getVariable())) {
continue;
}
answerForm.setDefaultAnswer(field.getVariable());
}
// Set the local variables according to the fields found in the answer form
if (answerForm.hasField(MUC_ROOMCONFIG_ROOMOWNERS)) {
// Set 'owners' to the currently configured owners
List<String> ownerStrings = answerForm.getField(MUC_ROOMCONFIG_ROOMOWNERS).getValues();
owners = new ArrayList<>(ownerStrings.size());
JidUtil.jidsFrom(ownerStrings, owners, null);
}
else {
// roomowners not supported, this should barely be the case
owners = null;
}
}
public boolean supportsRoomOwners() {
return owners != null;
}
public MucConfigFormManager setRoomOwners(Collection<? extends Jid> newOwners) throws MucConfigurationNotSupported {
if (!supportsRoomOwners()) {
throw new MucConfigurationNotSupported(MUC_ROOMCONFIG_ROOMOWNERS);
}
owners.clear();
owners.addAll(newOwners);
return this;
}
public void submitConfigurationForm() throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException {
if (owners != null) {
answerForm.setAnswer(MUC_ROOMCONFIG_ROOMOWNERS, JidUtil.toStringList(owners));
}
multiUserChat.sendConfigurationForm(answerForm);
}
}

View File

@ -57,6 +57,9 @@ import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.iqregister.packet.Registration;
import org.jivesoftware.smackx.muc.MultiUserChatException.MucAlreadyJoinedException;
import org.jivesoftware.smackx.muc.MultiUserChatException.MucNotJoinedException;
import org.jivesoftware.smackx.muc.MultiUserChatException.MissingMucCreationAcknowledgeException;
import org.jivesoftware.smackx.muc.packet.Destroy;
import org.jivesoftware.smackx.muc.packet.MUCAdmin;
import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
@ -354,25 +357,30 @@ public class MultiUserChat {
* the room. {@link #sendConfigurationForm(Form)}
*
* @param nickname the nickname to use.
* @return a handle to the MUC create configuration form API.
* @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.
* @throws InterruptedException
* @throws NotConnectedException
* @throws MucAlreadyJoinedException
* @throws MissingMucCreationAcknowledgeException
*/
public synchronized void create(Resourcepart nickname) throws NoResponseException, XMPPErrorException, SmackException, InterruptedException {
public synchronized MucCreateConfigFormHandle create(Resourcepart nickname) throws NoResponseException,
XMPPErrorException, InterruptedException, MucAlreadyJoinedException,
NotConnectedException, MissingMucCreationAcknowledgeException {
if (joined) {
throw new IllegalStateException("Creation failed - User already joined the room.");
throw new MucAlreadyJoinedException();
}
if (createOrJoin(nickname)) {
MucCreateConfigFormHandle mucCreateConfigFormHandle = createOrJoin(nickname);
if (mucCreateConfigFormHandle != null) {
// We successfully created a new room
return;
return mucCreateConfigFormHandle;
}
// 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.");
throw new MissingMucCreationAcknowledgeException();
}
/**
@ -380,15 +388,16 @@ public class MultiUserChat {
* discussion history and using the connections default reply timeout.
*
* @param nickname
* @return true if the room creation was acknowledged by the service, false otherwise.
* @return A {@link MucCreateConfigFormHandle} if the room was created, or {code null} if the room was joined.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws SmackException
* @throws InterruptedException
* @throws NotConnectedException
* @throws MucAlreadyJoinedException
* @see #createOrJoin(Resourcepart, String, DiscussionHistory, long)
*/
public synchronized boolean createOrJoin(Resourcepart nickname) throws NoResponseException, XMPPErrorException,
SmackException, InterruptedException {
public synchronized MucCreateConfigFormHandle createOrJoin(Resourcepart nickname) throws NoResponseException, XMPPErrorException,
InterruptedException, MucAlreadyJoinedException, NotConnectedException {
return createOrJoin(nickname, null, null, connection.getPacketReplyTimeout());
}
@ -402,16 +411,18 @@ public class MultiUserChat {
* @param password the password to use.
* @param history the amount of discussion history to receive while joining a room.
* @param timeout the amount of time to wait for a reply from the MUC service(in milliseconds).
* @return true if the room creation was acknowledged by the service, false otherwise.
* @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
*/
public synchronized boolean createOrJoin(Resourcepart nickname, String password, DiscussionHistory history, long timeout)
throws NoResponseException, XMPPErrorException, SmackException, InterruptedException {
public synchronized MucCreateConfigFormHandle createOrJoin(Resourcepart nickname, String password, DiscussionHistory history, long timeout)
throws NoResponseException, XMPPErrorException, InterruptedException, MucAlreadyJoinedException, NotConnectedException {
if (joined) {
throw new IllegalStateException("Creation failed - User already joined the room.");
throw new MucAlreadyJoinedException();
}
Presence presence = enter(nickname, password, history, timeout);
@ -420,9 +431,78 @@ public class MultiUserChat {
MUCUser mucUser = MUCUser.from(presence);
if (mucUser != null && mucUser.getStatus().contains(Status.ROOM_CREATED_201)) {
// Room was created and the user has joined the room
return true;
return new MucCreateConfigFormHandle();
}
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
* instant room, use {@link #makeInstant()}.
* <p>
* For advanced configuration options, use {@link MultiUserChat#getConfigurationForm()}, get the answer form with
* {@link Form#createAnswerForm()}, fill it out and send it back to the room with
* {@link MultiUserChat#sendConfigurationForm(Form)}.
* </p>
*/
public class MucCreateConfigFormHandle {
/**
* Create an instant room. The default configuration will be accepted and the room will become unlocked, i.e.
* other users are able to join.
*
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @see <a href="http://www.xmpp.org/extensions/xep-0045.html#createroom-instant">XEP-45 § 10.1.2 Creating an
* Instant Room</a>
*/
public void makeInstant() throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException {
sendConfigurationForm(new Form(DataForm.Type.submit));
}
/**
* Alias for {@link MultiUserChat#getConfigFormManager()}.
*
* @return a MUC configuration form manager for this room.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @see MultiUserChat#getConfigFormManager()
*/
public MucConfigFormManager getConfigFormManager() throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
return MultiUserChat.this.getConfigFormManager();
}
}
/**
* Create or join a MUC if it is necessary, i.e. if not the MUC is not already joined.
*
* @param nickname the required nickname to use.
* @param password the optional password required to join
* @return A {@link MucCreateConfigFormHandle} if the room was created, or {code null} if the room was joined.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
*/
public MucCreateConfigFormHandle createOrJoinIfNecessary(Resourcepart nickname, String password) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
if (isJoined()) {
return null;
}
try {
return createOrJoin(nickname, password, null, connection.getPacketReplyTimeout());
}
catch (MucAlreadyJoinedException e) {
return null;
}
return false;
}
/**
@ -466,10 +546,11 @@ public class MultiUserChat {
* 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 SmackException if there was no response from the server.
* @throws InterruptedException
* @throws NotConnectedException
* @throws NoResponseException if there was no response from the server.
*/
public void join(Resourcepart nickname, String password) throws XMPPErrorException, SmackException, InterruptedException {
public void join(Resourcepart nickname, String password) throws XMPPErrorException, InterruptedException, NoResponseException, NotConnectedException {
join(nickname, password, null, connection.getPacketReplyTimeout());
}
@ -547,6 +628,25 @@ public class MultiUserChat {
userHasLeft();
}
/**
* Get a {@link MucConfigFormManager} to configure this room.
* <p>
* Only room owners are able to configure a room.
* </p>
*
* @return a MUC configuration form manager for this room.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @see <a href="http://xmpp.org/extensions/xep-0045.html#roomconfig">XEP-45 § 10.2 Subsequent Room Configuration</a>
* @since 4.2
*/
public MucConfigFormManager getConfigFormManager() throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
return new MucConfigFormManager(this);
}
/**
* Returns the room's configuration form that the room's owner can use or <tt>null</tt> if
* no configuration is possible. The configuration form allows to set the room's language,
@ -570,14 +670,13 @@ public class MultiUserChat {
/**
* Sends the completed configuration form to the server. The room will be configured
* with the new settings defined in the form. If the form is empty then the server
* will create an instant room (will use default configuration).
* with the new settings defined in the form.
*
* @param form the form with the new settings.
* @throws XMPPErrorException if an error occurs setting the new rooms' configuration.
* @throws NoResponseException if there was no response from the server.
* @throws NotConnectedException
* @throws InterruptedException
* @throws InterruptedException
*/
public void sendConfigurationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
MUCOwner iq = new MUCOwner();
@ -868,13 +967,14 @@ public class MultiUserChat {
* @throws NoResponseException if there was no response from the server.
* @throws NotConnectedException
* @throws InterruptedException
* @throws MucNotJoinedException
*/
public void changeNickname(Resourcepart nickname) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
public void changeNickname(Resourcepart nickname) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, MucNotJoinedException {
StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank.");
// Check that we already have joined the room before attempting to change the
// nickname.
if (!joined) {
throw new IllegalStateException("Must be logged into the room to change nickname.");
throw new MucNotJoinedException(this);
}
final FullJid jid = JidCreate.fullFrom(room, nickname);
// We change the nickname by sending a presence packet where the "to"
@ -905,14 +1005,14 @@ public class MultiUserChat {
* @param mode the mode type for the presence update.
* @throws NotConnectedException
* @throws InterruptedException
* @throws MucNotJoinedException
*/
public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException, InterruptedException {
public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException, InterruptedException, MucNotJoinedException {
StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank.");
// Check that we already have joined the room before attempting to change the
// availability status.
if (!joined) {
throw new IllegalStateException(
"Must be logged into the room to change the " + "availability status.");
throw new MucNotJoinedException(this);
}
// We change the availability status by sending a presence packet to the room with the
// new presence status and mode
@ -1689,11 +1789,11 @@ public class MultiUserChat {
*
* @return the next message if one is immediately available and
* <tt>null</tt> otherwise.
* @throws MUCNotJoinedException
* @throws MucNotJoinedException
*/
public Message pollMessage() throws MUCNotJoinedException {
public Message pollMessage() throws MucNotJoinedException {
if (messageCollector == null) {
throw new MUCNotJoinedException(this);
throw new MucNotJoinedException(this);
}
return messageCollector.pollResult();
}
@ -1703,12 +1803,12 @@ public class MultiUserChat {
* (not return) until a message is available.
*
* @return the next message.
* @throws MUCNotJoinedException
* @throws MucNotJoinedException
* @throws InterruptedException
*/
public Message nextMessage() throws MUCNotJoinedException, InterruptedException {
public Message nextMessage() throws MucNotJoinedException, InterruptedException {
if (messageCollector == null) {
throw new MUCNotJoinedException(this);
throw new MucNotJoinedException(this);
}
return messageCollector.nextResult();
}
@ -1721,12 +1821,12 @@ public class MultiUserChat {
* @param timeout the maximum amount of time to wait for the next message.
* @return the next message, or <tt>null</tt> if the timeout elapses without a
* message becoming available.
* @throws MUCNotJoinedException
* @throws MucNotJoinedException
* @throws InterruptedException
*/
public Message nextMessage(long timeout) throws MUCNotJoinedException, InterruptedException {
public Message nextMessage(long timeout) throws MucNotJoinedException, InterruptedException {
if (messageCollector == null) {
throw new MUCNotJoinedException(this);
throw new MucNotJoinedException(this);
}
return messageCollector.nextResult(timeout);
}

View File

@ -0,0 +1,86 @@
/**
*
* Copyright © 2014-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 org.jivesoftware.smack.SmackException;
public abstract class MultiUserChatException extends SmackException {
protected MultiUserChatException() {
}
protected MultiUserChatException(String message) {
super(message);
}
/**
*
*/
private static final long serialVersionUID = 1L;
// This could eventually become an unchecked exception. But be aware that it's required in the
// control flow of MultiUserChat.createOrJoinIfNecessary
public static class MucAlreadyJoinedException extends MultiUserChatException {
/**
*
*/
private static final long serialVersionUID = 1L;
}
/**
* Thrown if the requested operation required the MUC to be joined by the
* client, while the client is currently joined.
*
*/
public static class MucNotJoinedException extends MultiUserChatException {
/**
*
*/
private static final long serialVersionUID = 1L;
public MucNotJoinedException(MultiUserChat multiUserChat) {
super("Client not currently joined " + multiUserChat.getRoom());
}
}
public static class MissingMucCreationAcknowledgeException extends MultiUserChatException {
/**
*
*/
private static final long serialVersionUID = 1L;
}
/**
* Thrown if the MUC room does not support the requested configuration option.
*/
public static class MucConfigurationNotSupported extends MultiUserChatException {
/**
*
*/
private static final long serialVersionUID = 1L;
public MucConfigurationNotSupported(String configString) {
super("The MUC configuration '" + configString + "' is not supported by the MUC service");
}
}
}

View File

@ -0,0 +1,145 @@
/**
*
* 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.bookmarkautojoin;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractConnectionListener;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
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.XMPPErrorException;
import org.jivesoftware.smackx.bookmarks.BookmarkManager;
import org.jivesoftware.smackx.bookmarks.BookmarkedConference;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.MultiUserChat.MucCreateConfigFormHandle;
import org.jivesoftware.smackx.muc.MultiUserChatManager;
import org.jxmpp.jid.parts.Resourcepart;
/**
* Autojoin bookmarked Multi-User Chat conferences.
*
* @see <a href="http://xmpp.org/extensions/xep-0048.html">XEP-48: Bookmarks</a>
*
*/
public final class MucBookmarkAutojoinManager extends Manager {
private static final Logger LOGGER = Logger.getLogger(MucBookmarkAutojoinManager.class.getName());
private static final Map<XMPPConnection, MucBookmarkAutojoinManager> INSTANCES = new WeakHashMap<>();
private static boolean autojoinEnabledDefault = false;
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
public void connectionCreated(XMPPConnection connection) {
getInstanceFor(connection);
}
});
}
public static void setAutojoinPerDefault(boolean autojoin) {
autojoinEnabledDefault = autojoin;
}
public static synchronized MucBookmarkAutojoinManager getInstanceFor(XMPPConnection connection) {
MucBookmarkAutojoinManager mbam = INSTANCES.get(connection);
if (mbam == null) {
mbam = new MucBookmarkAutojoinManager(connection);
INSTANCES.put(connection, mbam);
}
return mbam;
}
private final MultiUserChatManager multiUserChatManager;
private final BookmarkManager bookmarkManager;
private boolean autojoinEnabled = autojoinEnabledDefault;
private MucBookmarkAutojoinManager(XMPPConnection connection) {
super(connection);
multiUserChatManager = MultiUserChatManager.getInstanceFor(connection);
bookmarkManager = BookmarkManager.getBookmarkManager(connection);
connection.addConnectionListener(new AbstractConnectionListener() {
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
if (!autojoinEnabled) {
return;
}
// TODO handle resumed case?
autojoinBookmarkedConferences();
}
});
}
public void setAutojoinEnabled(boolean autojoin) {
autojoinEnabled = autojoin;
}
public void autojoinBookmarkedConferences() {
List<BookmarkedConference> bookmarkedConferences;
try {
bookmarkedConferences = bookmarkManager.getBookmarkedConferences();
}
catch (NotConnectedException | InterruptedException e) {
LOGGER.log(Level.FINER, "Could not get MUC bookmarks", e);
return;
}
catch (NoResponseException | XMPPErrorException e) {
LOGGER.log(Level.WARNING, "Could not get MUC bookmarks", e);
return;
}
final XMPPConnection connection = connection();
Resourcepart defaultNick = connection.getUser().getResourcepart();
for (BookmarkedConference bookmarkedConference : bookmarkedConferences) {
if (!bookmarkedConference.isAutoJoin()) {
continue;
}
Resourcepart nick = bookmarkedConference.getNickname();
if (nick == null) {
nick = defaultNick;
}
String password = bookmarkedConference.getPassword();
MultiUserChat muc = multiUserChatManager.getMultiUserChat(bookmarkedConference.getJid());
try {
MucCreateConfigFormHandle handle = muc.createOrJoinIfNecessary(nick, password);
if (handle != null) {
handle.makeInstant();
}
}
catch (NotConnectedException | InterruptedException e) {
LOGGER.log(Level.FINER, "Could not autojoin bookmarked MUC", e);
// abort here
break;
}
catch (NoResponseException | XMPPErrorException e) {
// Do no abort, just log,
LOGGER.log(Level.WARNING, "Could not autojoin bookmarked MUC", e);
}
}
}
}

View File

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014 Florian Schmaus
* 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.
@ -14,23 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.muc;
import org.jivesoftware.smack.SmackException;
/**
* Thrown if the requested operation required the MUC to be joined by the
* client, while the client is currently joined.
*
* Manager to autojoin bookmarked Multi-User Chat conferences.
*/
public class MUCNotJoinedException extends SmackException {
/**
*
*/
private static final long serialVersionUID = -5204934585663465576L;
public MUCNotJoinedException(MultiUserChat multiUserChat) {
super("Client not currently joined " + multiUserChat.getRoom());
}
}
package org.jivesoftware.smackx.muc.bookmarkautojoin;

View File

@ -353,6 +353,17 @@ public class Form {
return dataForm.getField(variable);
}
/**
* Check if a field with the given variable exists.
*
* @param variable the variable to check for.
* @return true if a field with the variable exists, false otherwise.
* @since 4.2
*/
public boolean hasField(String variable) {
return dataForm.hasField(variable);
}
/**
* Returns the instructions that explain how to fill out the form and what the form is about.
*

View File

@ -3,6 +3,7 @@
<className>org.jivesoftware.smackx.disco.ServiceDiscoveryManager</className>
<className>org.jivesoftware.smackx.xhtmlim.XHTMLManager</className>
<className>org.jivesoftware.smackx.muc.MultiUserChatManager</className>
<className>org.jivesoftware.smackx.muc.bookmarkautojoin.MucBookmarkAutojoinManager</className>
<className>org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager</className>
<className>org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager</className>
<className>org.jivesoftware.smackx.filetransfer.FileTransferManager</className>

View File

@ -30,8 +30,7 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.xdata.Form;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jivesoftware.smackx.muc.MultiUserChat.MucCreateConfigFormHandle;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
@ -82,9 +81,9 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
}
});
boolean newlyCreated = mucAsSeenByOne.createOrJoin(Resourcepart.from("one-" + randomString));
if (newlyCreated) {
mucAsSeenByOne.sendConfigurationForm(new Form(DataForm.Type.submit));
MucCreateConfigFormHandle handle = mucAsSeenByOne.createOrJoin(Resourcepart.from("one-" + randomString));
if (handle != null) {
handle.makeInstant();
}
mucAsSeenByTwo.join(Resourcepart.from("two-" + randomString));

View File

@ -0,0 +1,91 @@
/**
*
* 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 static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.igniterealtime.smack.inttest.AbstractSmackLowLevelIntegrationTest;
import org.igniterealtime.smack.inttest.Configuration;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.bookmarks.BookmarkManager;
import org.jivesoftware.smackx.muc.MultiUserChat.MucCreateConfigFormHandle;
import org.jivesoftware.smackx.muc.bookmarkautojoin.MucBookmarkAutojoinManager;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.jid.parts.Resourcepart;
public class MultiUserChatLowLevelIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
public MultiUserChatLowLevelIntegrationTest(Configuration configuration, String testRunId) throws Exception {
super(configuration, testRunId);
performCheck(new ConnectionCallback() {
@Override
public void connectionCallback(XMPPTCPConnection connection) throws Exception {
if (MultiUserChatManager.getInstanceFor(connection).getServiceNames().isEmpty()) {
throw new TestNotPossibleException("MUC component not offered by service");
}
}
});
}
@SmackIntegrationTest
public void testMucBookmarksAutojoin(XMPPTCPConnection connection) throws InterruptedException,
TestNotPossibleException, XMPPException, SmackException, IOException {
final BookmarkManager bookmarkManager = BookmarkManager.getBookmarkManager(connection);
if (!bookmarkManager.isSupported()) {
throw new TestNotPossibleException("Private data storage not supported");
}
final MultiUserChatManager multiUserChatManager = MultiUserChatManager.getInstanceFor(connection);
final Resourcepart mucNickname = Resourcepart.from("Nick-" + StringUtils.randomString(6));
final String randomMucName = StringUtils.randomString(6);
final DomainBareJid mucComponent = multiUserChatManager.getServiceNames().get(0);
final MultiUserChat muc = multiUserChatManager.getMultiUserChat(JidCreate.bareFrom(
Localpart.from(randomMucName), mucComponent));
MucCreateConfigFormHandle handle = muc.createOrJoin(mucNickname);
if (handle != null) {
handle.makeInstant();
}
muc.leave();
bookmarkManager.addBookmarkedConference("Smack Inttest: " + testRunId, muc.getRoom(), true,
mucNickname, null);
connection.disconnect();
connection.connect().login();
// MucBookmarkAutojoinManager is also able to do its task automatically
// after every login, it's not determinstic when this will be finished.
// So we trigger it manually here.
MucBookmarkAutojoinManager.getInstanceFor(connection).autojoinBookmarkedConferences();
assertTrue(muc.isJoined());
// If the test went well, leave the MUC
muc.leave();
}
}

View File

@ -2,7 +2,7 @@ allprojects {
ext {
shortVersion = '4.2.0-alpha2'
isSnapshot = true
jxmppVersion = '0.5.0-alpha2'
jxmppVersion = '0.5.0-alpha3'
smackMinAndroidSdk = 8
}
}