From fae9d129a9377de58455949c8d00107e9aa6f66e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Havlas?= Date: Sat, 14 Mar 2015 15:19:52 +0100 Subject: [PATCH] =?UTF-8?q?Added=20support=20for=20pre-approved=20subscrip?= =?UTF-8?q?tion=20requests=20(RFC=206121=20=C2=A7=203.4)=20SMACK-639?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../smack/AbstractXMPPConnection.java | 2 +- .../jivesoftware/smack/DummyConnection.java | 11 + .../org/jivesoftware/smack/roster/Roster.java | 71 +++++- .../smack/roster/RosterEntry.java | 17 +- .../smack/roster/packet/RosterPacket.java | 24 ++ .../packet/SubscriptionPreApproval.java | 49 +++++ .../roster/provider/RosterPacketProvider.java | 4 + ...ptionPreApprovalStreamFeatureProvider.java | 35 +++ .../rosterstore/DirectoryRosterStore.java | 9 + .../smackim.providers | 6 + .../roster/SubscriptionPreApprovalTest.java | 207 ++++++++++++++++++ .../rosterstore/DirectoryRosterStoreTest.java | 10 + 12 files changed, 440 insertions(+), 5 deletions(-) create mode 100644 smack-im/src/main/java/org/jivesoftware/smack/roster/packet/SubscriptionPreApproval.java create mode 100644 smack-im/src/main/java/org/jivesoftware/smack/roster/provider/SubscriptionPreApprovalStreamFeatureProvider.java create mode 100644 smack-im/src/test/java/org/jivesoftware/smack/roster/SubscriptionPreApprovalTest.java 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());