diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
index d15e996ac..2a5f708a8 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
@@ -1405,7 +1405,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return getFeature(element, namespace) != null;
}
- private void addStreamFeature(ExtensionElement feature) {
+ protected void addStreamFeature(ExtensionElement feature) {
String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace());
streamFeatures.put(key, feature);
}
diff --git a/smack-core/src/test/java/org/jivesoftware/smack/DummyConnection.java b/smack-core/src/test/java/org/jivesoftware/smack/DummyConnection.java
index c6596d509..df4b5e3a0 100644
--- a/smack-core/src/test/java/org/jivesoftware/smack/DummyConnection.java
+++ b/smack-core/src/test/java/org/jivesoftware/smack/DummyConnection.java
@@ -24,6 +24,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.PlainStreamElement;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
@@ -191,6 +192,16 @@ public class DummyConnection extends AbstractXMPPConnection {
invokePacketCollectorsAndNotifyRecvListeners(packet);
}
+ /**
+ * Enable stream feature.
+ *
+ * @param streamFeature the stream feature.
+ * @since 4.2
+ */
+ public void enableStreamFeature(ExtensionElement streamFeature) {
+ addStreamFeature(streamFeature);
+ }
+
public static DummyConnection newConnectedDummyConnection() {
DummyConnection dummyConnection = new DummyConnection();
try {
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
index 6e5c417bd..59fdaf9e3 100644
--- a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
@@ -40,6 +40,7 @@ import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
@@ -57,6 +58,7 @@ import org.jivesoftware.smack.packet.XMPPError.Condition;
import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jivesoftware.smack.roster.packet.RosterVer;
import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
+import org.jivesoftware.smack.roster.packet.SubscriptionPreApproval;
import org.jivesoftware.smack.roster.rosterstore.RosterStore;
import org.jivesoftware.smack.util.Objects;
import org.jxmpp.jid.BareJid;
@@ -456,6 +458,7 @@ public class Roster extends Manager {
* @param name the nickname of the user.
* @param groups the list of group names the entry will belong to, or null if the
* the roster entry won't belong to a group.
+ * @param approved the pre-approval state.
* @throws NoResponseException if there was no response from the server.
* @throws XMPPErrorException if an XMPP exception occurs.
* @throws NotLoggedInException If not logged in.
@@ -491,6 +494,68 @@ public class Roster extends Manager {
connection.sendStanza(presencePacket);
}
+ /**
+ * Creates a new pre-approved roster entry and presence subscription. The server will
+ * asynchronously update the roster with the subscription status.
+ *
+ * @param user the user. (e.g. johndoe@jabber.org)
+ * @param name the nickname of the user.
+ * @param groups the list of group names the entry will belong to, or null if the
+ * the roster entry won't belong to a group.
+ * @param approved the pre-approval state.
+ * @throws NoResponseException if there was no response from the server.
+ * @throws XMPPErrorException if an XMPP exception occurs.
+ * @throws NotLoggedInException if not logged in.
+ * @throws NotConnectedException
+ * @throws InterruptedException
+ * @throws FeatureNotSupportedException if pre-approving is not supported.
+ * @since 4.2
+ */
+ public void preApproveAndCreateEntry(Jid user, String name, String[] groups) throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, FeatureNotSupportedException {
+ preApprove(user);
+ createEntry(user, name, groups);
+ }
+
+ /**
+ * Pre-approve user presence subscription.
+ *
+ * @param user the user. (e.g. johndoe@jabber.org)
+ * @throws NotLoggedInException if not logged in.
+ * @throws NotConnectedException
+ * @throws InterruptedException
+ * @throws FeatureNotSupportedException if pre-approving is not supported.
+ * @since 4.2
+ */
+ public void preApprove(Jid user) throws NotLoggedInException, NotConnectedException, InterruptedException, FeatureNotSupportedException {
+ final XMPPConnection connection = connection();
+ if (!isSubscriptionPreApprovalSupported()) {
+ throw new FeatureNotSupportedException("Pre-approving");
+ }
+
+ Presence presencePacket = new Presence(Presence.Type.subscribed);
+ presencePacket.setTo(user);
+ connection.sendStanza(presencePacket);
+ }
+
+ /**
+ * Check for subscription pre-approval support.
+ *
+ * @return true if subscription pre-approval is supported by the server.
+ * @throws NotLoggedInException if not logged in.
+ * @since 4.2
+ */
+ public boolean isSubscriptionPreApprovalSupported() throws NotLoggedInException {
+ final XMPPConnection connection = connection();
+ if (!connection.isAuthenticated()) {
+ throw new NotLoggedInException();
+ }
+ if (connection.isAnonymous()) {
+ throw new IllegalStateException("Anonymous users can't have a roster.");
+ }
+
+ return connection.hasFeature(SubscriptionPreApproval.ELEMENT, SubscriptionPreApproval.NAMESPACE);
+ }
+
/**
* Removes a roster entry from the roster. The roster entry will also be removed from the
* unfiled entries or from any roster group where it could belong and will no longer be part
@@ -1329,7 +1394,7 @@ public class Roster extends Manager {
for (RosterPacket.Item item : validItems) {
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
- item.getItemType(), item.getItemStatus(), Roster.this, connection);
+ item.getItemType(), item.getItemStatus(), item.isApproved(), Roster.this, connection);
addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
}
@@ -1359,7 +1424,7 @@ public class Roster extends Manager {
// await possible further roster pushes.
for (RosterPacket.Item item : rosterStore.getEntries()) {
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
- item.getItemType(), item.getItemStatus(), Roster.this, connection);
+ item.getItemType(), item.getItemStatus(), item.isApproved(), Roster.this, connection);
addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
}
}
@@ -1429,7 +1494,7 @@ public class Roster extends Manager {
// safely retrieve this single item here.
Item item = items.iterator().next();
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
- item.getItemType(), item.getItemStatus(), Roster.this, connection);
+ item.getItemType(), item.getItemStatus(), item.isApproved(), Roster.this, connection);
String version = rosterPacket.getVersion();
if (item.getItemType().equals(RosterPacket.ItemType.remove)) {
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java
index 06e1665b6..5db749306 100644
--- a/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java
@@ -47,6 +47,7 @@ public class RosterEntry {
private String name;
private RosterPacket.ItemType type;
private RosterPacket.ItemStatus status;
+ private final boolean approved;
final private Roster roster;
final private XMPPConnection connection;
@@ -57,14 +58,16 @@ public class RosterEntry {
* @param name the nickname for the entry.
* @param type the subscription type.
* @param status the subscription status (related to subscriptions pending to be approbed).
+ * @param approved the pre-approval flag.
* @param connection a connection to the XMPP server.
*/
RosterEntry(Jid user, String name, RosterPacket.ItemType type,
- RosterPacket.ItemStatus status, Roster roster, XMPPConnection connection) {
+ RosterPacket.ItemStatus status, boolean approved, Roster roster, XMPPConnection connection) {
this.user = user;
this.name = name;
this.type = type;
this.status = status;
+ this.approved = approved;
this.roster = roster;
this.connection = connection;
}
@@ -124,6 +127,15 @@ public class RosterEntry {
this.status = status;
}
+ /**
+ * Returns the pre-approval state of this entry.
+ *
+ * @return the pre-approval state.
+ */
+ public boolean isApproved() {
+ return approved;
+ }
+
/**
* Returns an copied list of the roster groups that this entry belongs to.
*
@@ -244,6 +256,8 @@ public class RosterEntry {
}
else if (!user.equals(other.user))
return false;
+ if (approved != other.approved)
+ return false;
return true;
}
@@ -251,6 +265,7 @@ public class RosterEntry {
RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName());
item.setItemType(entry.getType());
item.setItemStatus(entry.getStatus());
+ item.setApproved(entry.isApproved());
// Set the correct group names for the item.
for (RosterGroup group : entry.getGroups()) {
item.addGroupName(group.getName());
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java
index 74e5afa25..e9b5197de 100644
--- a/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java
@@ -111,6 +111,7 @@ public class RosterPacket extends IQ {
private String name;
private ItemType itemType;
private ItemStatus itemStatus;
+ private boolean approved;
private final Set groupNames;
/**
@@ -190,6 +191,25 @@ public class RosterPacket extends IQ {
this.itemStatus = itemStatus;
}
+
+ /**
+ * Returns the roster item pre-approval state.
+ *
+ * @return the pre-approval state.
+ */
+ public boolean isApproved() {
+ return approved;
+ }
+
+ /**
+ * Sets the roster item pre-approval state.
+ *
+ * @param approved the pre-approval flag.
+ */
+ public void setApproved(boolean approved) {
+ this.approved = approved;
+ }
+
/**
* Returns an unmodifiable set of the group names that the roster item
* belongs to.
@@ -224,6 +244,7 @@ public class RosterPacket extends IQ {
xml.optAttribute("name", name);
xml.optAttribute("subscription", itemType);
xml.optAttribute("ask", itemStatus);
+ xml.optBooleanAttribute("approved", approved);
xml.rightAngleBracket();
for (String groupName : groupNames) {
@@ -242,6 +263,7 @@ public class RosterPacket extends IQ {
result = prime * result + ((itemType == null) ? 0 : itemType.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((user == null) ? 0 : user.hashCode());
+ result = prime * result + ((approved == false) ? 0 : 1);
return result;
}
@@ -276,6 +298,8 @@ public class RosterPacket extends IQ {
}
else if (!user.equals(other.user))
return false;
+ if (approved != other.approved)
+ return false;
return true;
}
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/SubscriptionPreApproval.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/SubscriptionPreApproval.java
new file mode 100644
index 000000000..0a6fddc05
--- /dev/null
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/SubscriptionPreApproval.java
@@ -0,0 +1,49 @@
+/**
+ *
+ * Copyright © 2015 Tomáš Havlas
+ *
+ * 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.smack.roster.packet;
+
+import org.jivesoftware.smack.packet.ExtensionElement;
+import org.jivesoftware.smack.util.XmlStringBuilder;
+
+public class SubscriptionPreApproval implements ExtensionElement {
+
+ public static final String ELEMENT = "sub";
+ public static final String NAMESPACE = "urn:xmpp:features:pre-approval";
+
+ public static final SubscriptionPreApproval INSTANCE = new SubscriptionPreApproval();
+
+ private SubscriptionPreApproval() {
+ }
+
+ @Override
+ public String getElementName() {
+ return ELEMENT;
+ }
+
+ @Override
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ @Override
+ public XmlStringBuilder toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder(this);
+ xml.closeEmptyElement();
+ return xml;
+ }
+
+}
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/provider/RosterPacketProvider.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/provider/RosterPacketProvider.java
index 941817e34..4ca729a22 100644
--- a/smack-im/src/main/java/org/jivesoftware/smack/roster/provider/RosterPacketProvider.java
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/provider/RosterPacketProvider.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.roster.packet.RosterPacket;
+import org.jivesoftware.smack.util.ParserUtils;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.xmlpull.v1.XmlPullParser;
@@ -59,6 +60,9 @@ public class RosterPacketProvider extends IQProvider {
String subscription = parser.getAttributeValue("", "subscription");
RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none");
item.setItemType(type);
+ // Set approval status.
+ boolean approved = ParserUtils.getBooleanAttribute(parser, "approved", false);
+ item.setApproved(approved);
break;
case "group":
// TODO item!= null
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/provider/SubscriptionPreApprovalStreamFeatureProvider.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/provider/SubscriptionPreApprovalStreamFeatureProvider.java
new file mode 100644
index 000000000..b00095b19
--- /dev/null
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/provider/SubscriptionPreApprovalStreamFeatureProvider.java
@@ -0,0 +1,35 @@
+/**
+ *
+ * Copyright © 2015 Tomáš Havlas
+ *
+ * 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.smack.roster.provider;
+
+import java.io.IOException;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.provider.ExtensionElementProvider;
+import org.jivesoftware.smack.roster.packet.SubscriptionPreApproval;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+public class SubscriptionPreApprovalStreamFeatureProvider extends ExtensionElementProvider {
+
+ @Override
+ public SubscriptionPreApproval parse(XmlPullParser parser, int initialDepth)
+ throws XmlPullParserException, IOException, SmackException {
+ return SubscriptionPreApproval.INSTANCE;
+ }
+
+}
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/rosterstore/DirectoryRosterStore.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/rosterstore/DirectoryRosterStore.java
index a89e33848..a826f6db6 100644
--- a/smack-im/src/main/java/org/jivesoftware/smack/roster/rosterstore/DirectoryRosterStore.java
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/rosterstore/DirectoryRosterStore.java
@@ -188,6 +188,7 @@ public class DirectoryRosterStore implements RosterStore {
String name = null;
String type = null;
String status = null;
+ String approved = null;
List groupNames = new ArrayList();
@@ -220,6 +221,10 @@ public class DirectoryRosterStore implements RosterStore {
parser.next();
status = parser.getText();
}
+ else if (parserName.equals("approved")) {
+ parser.next();
+ approved = parser.getText();
+ }
else if (parserName.equals("group")) {
parser.next();
parser.next();
@@ -275,6 +280,9 @@ public class DirectoryRosterStore implements RosterStore {
item.setItemStatus(itemStatus);
}
}
+ if (approved != null) {
+ item.setApproved(Boolean.parseBoolean(approved));
+ }
return item;
}
@@ -287,6 +295,7 @@ public class DirectoryRosterStore implements RosterStore {
xml.optElement("name", item.getName());
xml.optElement("type", item.getItemType());
xml.optElement("status", item.getItemStatus());
+ xml.optElement("approved", Boolean.toString(item.isApproved()));
for (String groupName : item.getGroupNames()) {
xml.openElement("group");
xml.element("groupName", groupName);
diff --git a/smack-im/src/main/resources/org.jivesoftware.smack.im/smackim.providers b/smack-im/src/main/resources/org.jivesoftware.smack.im/smackim.providers
index 41d2aa617..110cfaed1 100644
--- a/smack-im/src/main/resources/org.jivesoftware.smack.im/smackim.providers
+++ b/smack-im/src/main/resources/org.jivesoftware.smack.im/smackim.providers
@@ -7,6 +7,12 @@
org.jivesoftware.smack.roster.provider.RosterPacketProvider
+
+ sub
+ urn:xmpp:features:pre-approval
+ org.jivesoftware.smack.roster.provider.SubscriptionPreApprovalStreamFeatureProvider
+
+
ver
urn:xmpp:features:rosterver
diff --git a/smack-im/src/test/java/org/jivesoftware/smack/roster/SubscriptionPreApprovalTest.java b/smack-im/src/test/java/org/jivesoftware/smack/roster/SubscriptionPreApprovalTest.java
new file mode 100644
index 000000000..586f33226
--- /dev/null
+++ b/smack-im/src/test/java/org/jivesoftware/smack/roster/SubscriptionPreApprovalTest.java
@@ -0,0 +1,207 @@
+/**
+ *
+ * Copyright © 2015 Tomáš Havlas
+ *
+ * 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.smack.roster;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.jivesoftware.smack.DummyConnection;
+import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
+import org.jivesoftware.smack.im.InitSmackIm;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Stanza;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.IQ.Type;
+import org.jivesoftware.smack.roster.RosterTest.TestRosterListener;
+import org.jivesoftware.smack.roster.packet.RosterPacket;
+import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
+import org.jivesoftware.smack.roster.packet.RosterPacket.ItemType;
+import org.jivesoftware.smack.roster.packet.SubscriptionPreApproval;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.jxmpp.jid.Jid;
+import org.jxmpp.jid.impl.JidCreate;
+
+/**
+ * Tests that verifies the correct behavior of the pre-approval implementation.
+ *
+ * @see Pre-Approving a Subscription Request
+ * @author Tomáš Havlas
+ */
+public class SubscriptionPreApprovalTest extends InitSmackIm {
+
+ private DummyConnection connection;
+ private Roster roster;
+ private TestRosterListener rosterListener;
+
+ @Before
+ public void setUp() throws Exception {
+ connection = new DummyConnection();
+ connection.connect();
+ connection.login();
+ rosterListener = new TestRosterListener();
+ roster = Roster.getInstanceFor(connection);
+ roster.addRosterListener(rosterListener);
+ connection.setPacketReplyTimeout(1000 * 60 * 5);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ connection.disconnect();
+ connection = null;
+ }
+
+ @Test(expected=FeatureNotSupportedException.class)
+ public void testPreApprovalNotSupported() throws Throwable {
+ final Jid contactJID = JidCreate.from("preapproval@example.com");
+ roster.preApprove(contactJID);
+ }
+
+ @Test
+ public void testPreApproveAndCreate() throws Throwable {
+ final Jid contactJID = JidCreate.from("preapproval@example.com");
+ final String contactName = "PreApproval";
+ final String[] contactGroup = {};
+ connection.enableStreamFeature(SubscriptionPreApproval.INSTANCE);
+
+ final PreApproveAndCreateEntryResponder serverSimulator = new PreApproveAndCreateEntryResponder() {
+ @Override
+ void verifyRosterUpdateRequest(final RosterPacket updateRequest) {
+ final Item item = updateRequest.getRosterItems().iterator().next();
+ assertSame("The provided JID doesn't match the requested!",
+ contactJID,
+ item.getUser());
+ assertSame("The provided name doesn't match the requested!",
+ contactName,
+ item.getName());
+ assertSame("The provided group number doesn't match the requested!",
+ 0,
+ item.getGroupNames().size());
+ }
+
+ @Override
+ void verifyPreApprovalRequest(Presence preApproval) {
+ assertSame("The provided name doesn't match the requested!",
+ contactJID,
+ preApproval.getTo());
+ assertSame("The provided presence type is incorrect!",
+ Presence.Type.subscribed,
+ preApproval.getType());
+ }
+ };
+ serverSimulator.start();
+ roster.preApproveAndCreateEntry(contactJID, contactName, contactGroup);
+ serverSimulator.join();
+
+ // Check if an error occurred within the simulator
+ final Throwable exception = serverSimulator.getException();
+ if (exception != null) {
+ throw exception;
+ }
+ rosterListener.waitUntilInvocationOrTimeout();
+
+ // Verify the roster entry of the new contact
+ final RosterEntry addedEntry = roster.getEntry(contactJID);
+ assertNotNull("The new contact wasn't added to the roster!", addedEntry);
+ assertTrue("The roster listener wasn't invoked for the new contact!",
+ rosterListener.getAddedAddresses().contains(contactJID));
+ assertSame("Setup wrong name for the new contact!",
+ contactName,
+ addedEntry.getName());
+ assertSame("Setup wrong default subscription status!",
+ ItemType.none,
+ addedEntry.getType());
+ assertSame("The new contact should be member of exactly one group!",
+ 0,
+ addedEntry.getGroups().size());
+ }
+
+ /**
+ * This class can be used to simulate the server response for
+ * a pre approve request request.
+ */
+ private abstract class PreApproveAndCreateEntryResponder extends Thread {
+ private Throwable exception = null;
+
+ /**
+ * Overwrite this method to check if the received roster update request is valid.
+ *
+ * @param updateRequest the request which would be sent to the server.
+ */
+ abstract void verifyRosterUpdateRequest(final RosterPacket updateRequest);
+ /**
+ * Overwrite this method to check if recieved pre-approval request is valid
+ *
+ * @param preApproval the request which would be sent to server.
+ */
+ abstract void verifyPreApprovalRequest(final Presence preApproval);
+
+ public void run() {
+ try {
+ while (true) {
+ final Stanza packet = connection.getSentPacket();
+ if (packet instanceof RosterPacket && ((IQ) packet).getType() == Type.set) {
+ final RosterPacket rosterRequest = (RosterPacket) packet;
+
+ // Prepare and process the roster push
+ final RosterPacket rosterPush = new RosterPacket();
+ final Item item = rosterRequest.getRosterItems().iterator().next();
+ if (item.getItemType() != ItemType.remove) {
+ item.setItemType(ItemType.none);
+ }
+ rosterPush.setType(Type.set);
+ rosterPush.setTo(connection.getUser());
+ rosterPush.addRosterItem(item);
+ connection.processPacket(rosterPush);
+
+ // Create and process the IQ response
+ final IQ response = IQ.createResultIQ(rosterRequest);
+ connection.processPacket(response);
+
+ // Verify the roster update request
+ assertSame("A roster set MUST contain one and only one element.",
+ 1,
+ rosterRequest.getRosterItemCount());
+ verifyRosterUpdateRequest(rosterRequest);
+ break;
+ }
+ else if (packet instanceof Presence && ((Presence) packet).getType() == Presence.Type.subscribed) {
+ final Presence approval = (Presence) packet;
+ verifyPreApprovalRequest(approval);
+ }
+ }
+ }
+ catch (Throwable e) {
+ exception = e;
+ fail(e.getMessage());
+ }
+ }
+
+ /**
+ * Returns the exception or error if something went wrong.
+ *
+ * @return the Throwable exception or error that occurred.
+ */
+ public Throwable getException() {
+ return exception;
+ }
+ }
+}
diff --git a/smack-im/src/test/java/org/jivesoftware/smack/roster/rosterstore/DirectoryRosterStoreTest.java b/smack-im/src/test/java/org/jivesoftware/smack/roster/rosterstore/DirectoryRosterStoreTest.java
index d60c88577..d48010d31 100644
--- a/smack-im/src/test/java/org/jivesoftware/smack/roster/rosterstore/DirectoryRosterStoreTest.java
+++ b/smack-im/src/test/java/org/jivesoftware/smack/roster/rosterstore/DirectoryRosterStoreTest.java
@@ -107,6 +107,8 @@ public class DirectoryRosterStoreTest {
item1.getItemType(), storedItem.getItemType());
assertEquals("ItemStatus of added entry",
item1.getItemStatus(), storedItem.getItemStatus());
+ assertEquals("Approved of added entry",
+ item1.isApproved(), storedItem.isApproved());
final String version2 = "2";
@@ -115,6 +117,7 @@ public class DirectoryRosterStoreTest {
item2.addGroupName("examples");
item2.setItemStatus(ItemStatus.subscribe);
item2.setItemType(ItemType.none);
+ item2.setApproved(true);
store.addEntry(item2,version2);
assertEquals("Updating entry sets version correctly", version2, store.getRosterVersion());
storedItem = store.getEntry(userName);
@@ -128,6 +131,8 @@ public class DirectoryRosterStoreTest {
item2.getItemType(), storedItem.getItemType());
assertEquals("ItemStatus of added entry",
item2.getItemStatus(), storedItem.getItemStatus());
+ assertEquals("Approved of added entry",
+ item2.isApproved(), storedItem.isApproved());
List- entries = store.getEntries();
assertEquals("Number of entries", 1, entries.size());
@@ -143,6 +148,7 @@ public class DirectoryRosterStoreTest {
item4.addGroupName("Bar Friends");
item4.setItemStatus(ItemStatus.subscribe);
item4.setItemType(ItemType.both);
+ item4.setApproved(true);
ArrayList
- items34 = new ArrayList();
items34.add(item3);
@@ -162,6 +168,8 @@ public class DirectoryRosterStoreTest {
item3.getItemType(), storedItem.getItemType());
assertEquals("ItemStatus of added entry",
item3.getItemStatus(), storedItem.getItemStatus());
+ assertEquals("Approved of added entry",
+ item3.isApproved(), storedItem.isApproved());
storedItem = store.getEntry(JidTestUtil.BARE_JID_2);
@@ -175,6 +183,8 @@ public class DirectoryRosterStoreTest {
item4.getItemType(), storedItem.getItemType());
assertEquals("ItemStatus of added entry",
item4.getItemStatus(), storedItem.getItemStatus());
+ assertEquals("Approved of added entry",
+ item4.isApproved(), storedItem.isApproved());
entries = store.getEntries();
assertEquals("Number of entries", 2, entries.size());