From 3062dc15ff8b8103ee69c2f425913a81bd3d082e Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Fri, 12 Apr 2024 16:19:42 +0200 Subject: [PATCH] [sinttest] Applied review feedback & new Smack features Flow's feedback has been applied, while new Smack features (tagging, better assertion messages) have also been applied. --- .../smack/roster/RosterIntegrationTest.java | 292 ++++++------------ 1 file changed, 95 insertions(+), 197 deletions(-) diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java index b371e1c8a2..f03ac2fcdb 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/roster/RosterIntegrationTest.java @@ -16,29 +16,40 @@ */ package org.jivesoftware.smack.roster; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.Collection; -import java.util.List; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.FromMatchesFilter; import org.jivesoftware.smack.filter.PresenceTypeFilter; import org.jivesoftware.smack.filter.StanzaTypeFilter; +import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; +import org.jivesoftware.smack.iqrequest.IQRequestHandler; +import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.PresenceBuilder; +import org.jivesoftware.smack.roster.packet.RosterPacket; import org.jivesoftware.smack.roster.packet.RosterPacket.ItemType; +import org.jivesoftware.smack.sm.predicates.ForEveryStanza; import org.jivesoftware.smack.util.Consumer; import org.jivesoftware.smack.util.StringUtils; import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.annotations.SpecificationReference; import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil; +import org.igniterealtime.smack.inttest.util.ResultSyncPoint; import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.Jid; +@SpecificationReference(document = "RFC6121") public class RosterIntegrationTest extends AbstractSmackIntegrationTest { private final Roster rosterOne; @@ -119,53 +130,29 @@ private void checkIfAddedAndSubscribed(Collection addresses) { * Asserts that when a user sends out a presence subscription request, the server sends a roster push back to the * user. * - *

From RFC6121 § 3.1.2:

- *
- * After locally delivering or remotely routing the presence subscription request, the user's server MUST then send - * a roster push to all of the user's interested resources, containing the potential contact with a subscription - * state of "none" and with notation that the subscription is pending (via an 'ask' attribute whose value is - * "subscribe"). - *
- * * @throws Exception when errors occur */ - @SmackIntegrationTest + @SmackIntegrationTest(section = "3.1.2", quote = + "After locally delivering or remotely routing the presence subscription request, the user's server MUST then " + + "send a roster push to all of the user's interested resources, containing the potential contact with a " + + "subscription state of \"none\" and with notation that the subscription is pending (via an 'ask' attribute " + + "whose value is \"subscribe\").") public void testRosterPushAfterSubscriptionRequest() throws Exception { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); + Roster.getInstanceFor(conTwo).setSubscriptionMode(Roster.SubscriptionMode.manual); // prevents a race condition when asserting the captured roster entry. + final ResultSyncPoint added = new ResultSyncPoint<>(); - final SimpleResultSyncPoint added = new SimpleResultSyncPoint(); final RosterListener rosterListener = new AbstractRosterListener() { @Override public void entriesAdded(Collection addresses) { - checkIfAdded(addresses); - } - @Override - public void entriesUpdated(Collection addresses) { - checkIfAdded(addresses); - } - private void checkIfAdded(Collection addresses) { for (Jid jid : addresses) { if (!jid.equals(conTwo.getUser().asBareJid())) { continue; } - BareJid bareJid = conTwo.getUser().asBareJid(); + final BareJid bareJid = conTwo.getUser().asBareJid(); RosterEntry rosterEntry = rosterOne.getEntry(bareJid); - if (rosterEntry == null) { - added.signalFailure("No roster entry for " + bareJid); - return; - } - - if (rosterEntry.getType() != ItemType.none) { - added.signalFailure("Incorrect subscription type on roster entry: " + rosterEntry.getType()); - return; - } - - if (!rosterEntry.isSubscriptionPending()) { - added.signalFailure("No 'ask' on roster entry."); - return; - } - - added.signal(); + added.signal(rosterEntry); + return; } } }; @@ -179,8 +166,11 @@ private void checkIfAdded(Collection addresses) { try { conOne.sendStanza(subscribe); - assertTrue(added.waitForResult(2 * connection.getReplyTimeout())); + final RosterEntry rosterEntry = assertResult(added, 2 * connection.getReplyTimeout(), "Expected the server to send a roster push back to '" + conOne.getUser() + "' after they sent a presence subscription request to '" + conTwo.getUser().asBareJid() + "' (but the server did not)."); + assertEquals(ItemType.none, rosterEntry.getType(), "Unexpected subscription type on roster push after '" + conOne.getUser() + "' sent a presence subscription request to '" + conTwo.getUser().asBareJid() + "'."); + assertTrue(rosterEntry.isSubscriptionPending(), "Missing 'ask=subscribe' attribute on roster push after '" + conOne.getUser() + "' sent a presence subscription request to '" + conTwo.getUser().asBareJid() + "'."); } finally { + rosterTwo.setSubscriptionMode(Roster.getDefaultSubscriptionMode()); rosterOne.removeRosterListener(rosterListener); } } @@ -189,30 +179,17 @@ private void checkIfAdded(Collection addresses) { * Asserts that when a user sends out a presence subscription request to an entity for which the user does not have * a pre-existing subscription, the server will deliver the subscription request to that entity. * - *

From RFC6121 § 3.1.3:

- *
- * if there is at least one available resource associated with the contact when the subscription request is received - * by the contact's server, then the contact's server MUST send that subscription request to all available resources - * in accordance with Section 8. - *
- * * @throws Exception when errors occur */ - @SmackIntegrationTest + @SmackIntegrationTest(section = "3.1.3", quote = + "if there is at least one available resource associated with the contact when the subscription request is " + + "received by the contact's server, then the contact's server MUST send that subscription request to all " + + "available resources in accordance with Section 8.") public void testPresenceDeliveredToRecipient() throws Exception { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); - final SimpleResultSyncPoint added = new SimpleResultSyncPoint(); - - final StanzaListener stanzaListener = stanza -> { - final Presence presence = (Presence) stanza; - if (presence.getType() != Presence.Type.subscribe) { - added.signalFailure("Incorrect subscription type on auto-reply: " + presence.getType()); - } else { - added.signal(); - } - }; - + final ResultSyncPoint added = new ResultSyncPoint<>(); + final StanzaListener stanzaListener = stanza -> added.signal((Presence) stanza); conTwo.addAsyncStanzaListener(stanzaListener, new AndFilter(StanzaTypeFilter.PRESENCE, FromMatchesFilter.createBare(conOne.getUser()))); final Presence subscribe = PresenceBuilder.buildPresence() @@ -222,8 +199,8 @@ public void testPresenceDeliveredToRecipient() throws Exception { try { conOne.sendStanza(subscribe); - - assertTrue(added.waitForResult(2 * connection.getReplyTimeout())); + final Presence received = assertResult(added, "Expected subscription request from '" + conOne.getUser() + "' to '" + conTwo.getUser().asBareJid() + "' to be delivered to " + conTwo.getUser() + " (but it did not)."); + assertEquals(Presence.Type.subscribe, received.getType(), "Unexpected presence type in presence stanza received by '" + conTwo.getUser() + "' after '" + conOne.getUser() + "' sent a presence subscription request."); } finally { conTwo.removeAsyncStanzaListener(stanzaListener); } @@ -233,16 +210,12 @@ public void testPresenceDeliveredToRecipient() throws Exception { * Asserts that when a user sends a presence subscription approval, the server stamps the bare JID of the sender, * and delivers it to the requester. * - *

From RFC6121 § 3.1.5:

- *
- * When the contact's client sends the subscription approval, the contact's server MUST stamp the outbound stanza - * with the bare JID <contact@domainpart> of the contact and locally deliver or remotely route the stanza to the - * user. - *
- * * @throws Exception when errors occur */ - @SmackIntegrationTest + @SmackIntegrationTest(section = "3.1.5", quote = + "When the contact's client sends the subscription approval, the contact's server MUST stamp the outbound " + + "stanza with the bare JID of the contact and locally deliver or remotely route the " + + "stanza to the user.") public void testPresenceApprovalStampedAndDelivered() throws Exception { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); @@ -252,18 +225,10 @@ public void testPresenceApprovalStampedAndDelivered() throws Exception { final Consumer interceptor = (PresenceBuilder presenceBuilder) -> presenceBuilder.to(conOne.getUser()).build(); conTwo.addPresenceInterceptor(interceptor, p -> p.getType() == Presence.Type.subscribed); - final SimpleResultSyncPoint added = new SimpleResultSyncPoint(); - - final StanzaListener stanzaListener = stanza -> { - final Presence presence = (Presence) stanza; - if (!presence.getFrom().isEntityBareJid()) { - added.signalFailure("'from' address should be a bare JID, but is a full JID."); - } else { - added.signal(); - } - }; + final ResultSyncPoint added = new ResultSyncPoint<>(); + final StanzaListener stanzaListener = stanza -> added.signal((Presence) stanza); - conOne.addAsyncStanzaListener(stanzaListener, new AndFilter(PresenceTypeFilter.SUBSCRIBED, FromMatchesFilter.createBare(conTwo.getUser()))); + conOne.addAsyncStanzaListener(stanzaListener, PresenceTypeFilter.SUBSCRIBED); final Presence subscribe = PresenceBuilder.buildPresence() .ofType(Presence.Type.subscribe) @@ -273,7 +238,9 @@ public void testPresenceApprovalStampedAndDelivered() throws Exception { try { conOne.sendStanza(subscribe); - assertTrue(added.waitForResult(2 * connection.getReplyTimeout())); + final Presence received = assertResult(added, "Expected presence 'subscribed' stanza to be delivered to '" + conOne.getUser() + "' after '" + conTwo.getUser() + "' approved their subscription request (but it was not)."); + assertEquals(conTwo.getUser().asBareJid(), received.getFrom().asEntityBareJidOrThrow(), "Expected presence 'subscribed' stanza that was delivered to '" + conOne.getUser() + "' after '" + conTwo.getUser() + "' approved their subscription request to have a 'from' attribute value that is associated to '" + conTwo.getUser().getLocalpart() + "' (but it did not)."); + assertTrue(received.getFrom().isEntityBareJid(), "Expected presence 'subscribed' stanza that was delivered to '" + conOne.getUser() + "' after '" + conTwo.getUser() + "' approved their subscription request to have a 'from' attribute value that is a bare JID (but it was not)."); } finally { rosterTwo.setSubscriptionMode(Roster.getDefaultSubscriptionMode()); conTwo.removePresenceInterceptor(interceptor); @@ -283,49 +250,30 @@ public void testPresenceApprovalStampedAndDelivered() throws Exception { /** * Asserts that when a user sends a presence subscription approval, the server sends a roster push to the user with - * a subscription 'from' - * - *

From RFC6121 § 3.1.5:

- *
- * The contact's server then MUST send an updated roster push to all of the contact's interested resources, with the - * 'subscription' attribute set to a value of "from". - *
+ * a subscription 'from'. * * @throws Exception when errors occur */ - @SmackIntegrationTest + @SmackIntegrationTest(section = "3.1.5", quote = + "The contact's server then MUST send an updated roster push to all of the contact's interested resources, " + + "with the 'subscription' attribute set to a value of \"from\".") public void testPresenceApprovalYieldsRosterPush() throws Exception { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); rosterTwo.setSubscriptionMode(Roster.SubscriptionMode.accept_all); - final SimpleResultSyncPoint added = new SimpleResultSyncPoint(); + final ResultSyncPoint updated = new ResultSyncPoint<>(); final RosterListener rosterListener = new AbstractRosterListener() { @Override public void entriesAdded(Collection addresses) { - checkIfAdded(addresses); - } - @Override - public void entriesUpdated(Collection addresses) { - checkIfAdded(addresses); - } - private void checkIfAdded(Collection addresses) { for (Jid jid : addresses) { if (!jid.equals(conOne.getUser().asBareJid())) { continue; } BareJid bareJid = conOne.getUser().asBareJid(); RosterEntry rosterEntry = rosterTwo.getEntry(bareJid); - if (rosterEntry == null) { - added.signalFailure("No roster entry for " + bareJid); - return; - } - if (!rosterEntry.getType().equals(ItemType.from)) { - added.signalFailure("Incorrect roster entry type. Expected 'from', got: " + rosterEntry.getType()); - return; - } - added.signal(); + updated.signal(rosterEntry); } } }; @@ -338,8 +286,10 @@ private void checkIfAdded(Collection addresses) { try { conOne.sendStanza(subscribe); + // The 'subscribe' gets automatically approved by conTwo. - assertTrue(added.waitForResult(2 * connection.getReplyTimeout())); + final RosterEntry entry = assertResult(updated, "Expected '" + conTwo.getUser() + "' to receive a roster push with an update for the entry of '" + conOne.getUser().asBareJid() + "' after '" + conTwo.getUser() + "' approved their subscription request."); + assertEquals(ItemType.from, entry.getType(), "Unexpected type for '" + conOne.getUser().asBareJid() + "''s entry in '" + conTwo.getUser().asBareJid() + "''s roster."); } finally { rosterTwo.setSubscriptionMode(Roster.getDefaultSubscriptionMode()); rosterTwo.removeRosterListener(rosterListener); @@ -350,17 +300,13 @@ private void checkIfAdded(Collection addresses) { * Asserts that when a user sends a presence subscription approval, the server sends a roster push to the user with * a subscription 'both' when the contact already has a subscription to the other entity. * - *

From RFC6121 § 3.1.5:

- *
- * The contact's server then MUST send an updated roster push to all of the contact's interested resources, with the - * 'subscription' attribute set to a value of "from". (Here we assume that the contact does not already have a - * subscription to the user; if that were the case, the 'subscription' attribute would be set to a value of "both", - * as explained under Appendix A.) - *
- * * @throws Exception when errors occur */ - @SmackIntegrationTest + @SmackIntegrationTest(section = "3.1.5", quote = + "The contact's server then MUST send an updated roster push to all of the contact's interested resources, " + + "with the 'subscription' attribute set to a value of \"from\". (Here we assume that the contact does not " + + "already have a subscription to the user; if that were the case, the 'subscription' attribute would be set " + + "to a value of \"both\", as explained under Appendix A.)") public void testPresenceApprovalYieldsRosterPush2() throws Exception { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); @@ -412,33 +358,17 @@ private void checkIfAdded(Collection addresses) { // Setup fixture is now complete. Execute the test. rosterTwo.setSubscriptionMode(Roster.SubscriptionMode.accept_all); - final SimpleResultSyncPoint added = new SimpleResultSyncPoint(); + final ResultSyncPoint updated = new ResultSyncPoint<>(); rosterListenerTwo = new AbstractRosterListener() { - @Override - public void entriesAdded(Collection addresses) { - checkIfAdded(addresses); - } @Override public void entriesUpdated(Collection addresses) { - checkIfAdded(addresses); - } - private void checkIfAdded(Collection addresses) { for (Jid jid : addresses) { if (!jid.equals(conOne.getUser().asBareJid())) { continue; } BareJid bareJid = conOne.getUser().asBareJid(); - RosterEntry rosterEntry = rosterTwo.getEntry(bareJid); - if (rosterEntry == null) { - added.signalFailure("No roster entry for " + bareJid); - return; - } - if (!rosterEntry.getType().equals(ItemType.both)) { - added.signalFailure("Incorrect roster entry type. Expected 'both', got: " + rosterEntry.getType()); - return; - } - added.signal(); + updated.signal(rosterTwo.getEntry(bareJid)); } } }; @@ -452,7 +382,8 @@ private void checkIfAdded(Collection addresses) { try { conOne.sendStanza(subscribeTwo); - assertTrue(added.waitForResult(2 * connection.getReplyTimeout())); + final RosterEntry entry = assertResult(updated, "Expected '" + conTwo.getUser() + "' to receive a roster push with an update for the entry of '" + conOne.getUser().asBareJid() + "' after '" + conOne.getUser() + "' approved their subscription request."); + assertEquals(ItemType.both, entry.getType(), "Unexpected type for '" + conOne.getUser().asBareJid() + "''s entry in '" + conTwo.getUser().asBareJid() + "''s roster."); } finally { rosterTwo.setSubscriptionMode(Roster.getDefaultSubscriptionMode()); rosterTwo.removeRosterListener(rosterListenerTwo); @@ -463,15 +394,10 @@ private void checkIfAdded(Collection addresses) { * Asserts that when a presence subscription request is approved, the server sends the latest presence of the now * subscribed entity to the subscriber. * - *

From RFC6121 § 3.1.5:

- *
- * The contact's server MUST then also send current presence to the user from each of the contact's available - * resources. - *
- * * @throws Exception when errors occur */ - @SmackIntegrationTest + @SmackIntegrationTest(section = "3.1.5", quote = + "The contact's server MUST then also send current presence to the user from each of the contact's available resources.") public void testCurrentPresenceSentAfterSubscriptionApproval() throws Exception { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); @@ -497,7 +423,7 @@ public void testCurrentPresenceSentAfterSubscriptionApproval() throws Exception try { conOne.sendStanza(subscribe); - assertTrue(received.waitForResult(2 * connection.getReplyTimeout())); + assertResult(received, "Expected '" + conTwo.getUser() + "' to receive '" + conOne.getUser() + "''s current presence update (including the status '" + needle + "'), but it did not."); } finally { rosterTwo.setSubscriptionMode(Roster.getDefaultSubscriptionMode()); conOne.removeAsyncStanzaListener(stanzaListener); @@ -508,74 +434,46 @@ public void testCurrentPresenceSentAfterSubscriptionApproval() throws Exception * Asserts that when a user receives a presence subscription approval, the server first sends the presence stanza, * followed by a roster push. * - *

From RFC6121 § 3.1.6:

- *
- * (...) If this check is successful, then the user's server MUST: - * 1. Deliver the inbound subscription approval to all of the user's interested resources (...). This MUST occur - * before sending the roster push described in the next step. - * 2. Initiate a roster push to all of the user's interested resources, containing an updated roster item for the - * contact with the 'subscription' attribute set to a value of "to" (...) or "both" (...). - *
- * * @throws Exception when errors occur */ - @SmackIntegrationTest + @SmackIntegrationTest(section = "3.1.6", quote = + "(...) If this check is successful, then the user's server MUST: 1. Deliver the inbound subscription " + + "approval to all of the user's interested resources (...). This MUST occur before sending the roster push " + + "described in the next step. 2. Initiate a roster push to all of the user's interested resources, containing " + + "an updated roster item for the contact with the 'subscription' attribute set to a value of \"to\" (...) or " + + "\"both\" (...).") public void testReceivePresenceApprovalAndRosterPush() throws Exception { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); rosterTwo.setSubscriptionMode(Roster.SubscriptionMode.accept_all); - final SimpleResultSyncPoint receivedPresence = new SimpleResultSyncPoint(); - final SimpleResultSyncPoint receivedRosterPush = new SimpleResultSyncPoint(); + final ResultSyncPoint receivedRosterPush = new ResultSyncPoint<>(); + final AtomicBoolean hasReceivedSubscriptionApproval = new AtomicBoolean(false); - final StanzaListener stanzaListener = stanza -> { - try { - receivedRosterPush.waitForResult(0); - // roster push should NOT have been received yet. If it has, fail the test. - receivedPresence.signalFailure("Received roster push before subscription approval."); - } catch (TimeoutException e) { - // Expected - receivedPresence.signal(); - } catch (Exception e) { - receivedPresence.signalFailure("Unexpected exception: " + e.getMessage()); + // Replace the IQ Request Handler that processes roster pushes with one that's guaranteed to be executed + // synchronously with the stanza handler that listens for presence updates (below). This is to guarantee that + // the order in which both stanzas arrive is maintained during processing. + final IQRequestHandler synchronousReplacement = new AbstractIqRequestHandler(RosterPacket.ELEMENT, RosterPacket.NAMESPACE, IQ.Type.set, IQRequestHandler.Mode.sync) { + @Override + public IQ handleIQRequest(IQ iqRequest) { + receivedRosterPush.signal(hasReceivedSubscriptionApproval.get()); + return IQ.createResultIQ(iqRequest); } }; + final IQRequestHandler originalRequestHandler = conOne.registerIQRequestHandler(synchronousReplacement); - // Add as a synchronous listener, as this test asserts the order of stanzas. - conOne.addSyncStanzaListener(stanzaListener, new AndFilter(PresenceTypeFilter.SUBSCRIBED, FromMatchesFilter.createBare(conTwo.getUser()))); - - final RosterListener rosterListener = new AbstractRosterListener() { - @Override - public void entriesAdded(Collection addresses) { - checkIfAdded(addresses); - } - @Override - public void entriesUpdated(Collection addresses) { - checkIfAdded(addresses); - } - private void checkIfAdded(Collection addresses) { - for (Jid jid : addresses) { - if (!jid.equals(conTwo.getUser().asBareJid())) { - continue; - } - BareJid bareJid = conTwo.getUser().asBareJid(); - if (!List.of(ItemType.to, ItemType.both).contains(rosterOne.getEntry(bareJid).getType())) { - receivedRosterPush.signalFailure("Incorrect roster entry type. Expected 'to' or 'both'"); - return; - } - try { - receivedPresence.waitForResult(0); - receivedRosterPush.signal(); - } catch (TimeoutException e) { - // subscription approval should have been received by now. If not, fail the test. - receivedRosterPush.signalFailure("Received roster push before subscription approval."); - } catch (Exception e) { - receivedRosterPush.signalFailure("Unexpected exception: " + e.getMessage()); - } + final StanzaListener presenceUpdateListener = stanza -> { + if (stanza instanceof Presence) { + final Presence presence = (Presence) stanza; + if (presence.getType() == Presence.Type.subscribed) { + hasReceivedSubscriptionApproval.set(true); } } }; - rosterOne.addRosterListener(rosterListener); + + // Add as a synchronous listener, again to guarantee to maintain the order of the stanzas captured by this + // listener and the roster pushes captured by the IQ Request Handler (above). + conOne.addSyncStanzaListener(presenceUpdateListener, ForEveryStanza.INSTANCE); final Presence subscribe = PresenceBuilder.buildPresence() .ofType(Presence.Type.subscribe) @@ -584,12 +482,12 @@ private void checkIfAdded(Collection addresses) { try { conOne.sendStanza(subscribe); - assertTrue(receivedPresence.waitForResult(2 * connection.getReplyTimeout())); - assertTrue(receivedRosterPush.waitForResult(connection.getReplyTimeout())); + final boolean hasReceivedApproval = assertResult(receivedRosterPush, "Expected '" + conOne.getUser() + "' to receive a roster push containing an updated roster entry for '" + conTwo.getUser().asBareJid() + "'"); + assertTrue(hasReceivedApproval, "Expected '" + conOne.getUser() + "' to have received a presence subscription approval from '" + conTwo.getUser().asBareJid() + "', before it received the roster push that contained the updated contact, but the presence subscription approval was not (yet) received when the roster push was."); } finally { rosterOne.setSubscriptionMode(Roster.getDefaultSubscriptionMode()); - rosterOne.removeRosterListener(rosterListener); - conOne.removeSyncStanzaListener(stanzaListener); + conOne.removeSyncStanzaListener(presenceUpdateListener); + conOne.registerIQRequestHandler(originalRequestHandler); } } }